authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-27 14:10:46+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-27 14:10:46+01:00
loge55e6b5528bb2f01de242fcf32b172e244e98e74
tree3a5eb3193d3d192c54ab0c2b7295a7f21861c27e
parentc3f2de5e519926eb0029062fe8e782a6f9df9c05
parent60a1ba0a8f3517356fa2941462f002a7f580545b

Merge pull request 'std: migrate all `fs` APIs to `Io`' (#30232) from std.Io-fs into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30232

280 files changed, 20282 insertions(+), 18623 deletions(-)

CMakeLists.txt-3
......@@ -436,9 +436,6 @@ set(ZIG_STAGE2_SOURCES
436436 lib/std/fmt.zig
437437 lib/std/fmt/parse_float.zig
438438 lib/std/fs.zig
439 lib/std/fs/AtomicFile.zig
440 lib/std/fs/Dir.zig
441 lib/std/fs/File.zig
442439 lib/std/fs/get_app_data_dir.zig
443440 lib/std/fs/path.zig
444441 lib/std/hash.zig
build.zig+27-68
......@@ -1,18 +1,20 @@
11const std = @import("std");
22const builtin = std.builtin;
3const tests = @import("test/tests.zig");
43const BufMap = std.BufMap;
54const mem = std.mem;
6const io = std.io;
75const fs = std.fs;
86const InstallDirectoryOptions = std.Build.InstallDirectoryOptions;
97const assert = std.debug.assert;
8const Io = std.Io;
9
10const tests = @import("test/tests.zig");
1011const DevEnv = @import("src/dev.zig").Env;
11const ValueInterpretMode = enum { direct, by_name };
1212
1313const zig_version: std.SemanticVersion = .{ .major = 0, .minor = 16, .patch = 0 };
1414const stack_size = 46 * 1024 * 1024;
1515
16const ValueInterpretMode = enum { direct, by_name };
17
1618pub fn build(b: *std.Build) !void {
1719 const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false;
1820 const target = b.standardTargetOptions(.{
......@@ -306,8 +308,10 @@ pub fn build(b: *std.Build) !void {
306308
307309 if (enable_llvm) {
308310 const cmake_cfg = if (static_llvm) null else blk: {
311 const io = b.graph.io;
312 const cwd: Io.Dir = .cwd();
309313 if (findConfigH(b, config_h_path_option)) |config_h_path| {
310 const file_contents = fs.cwd().readFileAlloc(config_h_path, b.allocator, .limited(max_config_h_bytes)) catch unreachable;
314 const file_contents = cwd.readFileAlloc(io, config_h_path, b.allocator, .limited(max_config_h_bytes)) catch unreachable;
311315 break :blk parseConfigH(b, file_contents);
312316 } else {
313317 std.log.warn("config.h could not be located automatically. Consider providing it explicitly via \"-Dconfig_h\"", .{});
......@@ -531,10 +535,6 @@ pub fn build(b: *std.Build) !void {
531535 .aarch64 => 701_413_785,
532536 else => 800_000_000,
533537 },
534 .windows => switch (b.graph.host.result.cpu.arch) {
535 .x86_64 => 536_414_208,
536 else => 600_000_000,
537 },
538538 else => 900_000_000,
539539 },
540540 }));
......@@ -561,30 +561,7 @@ pub fn build(b: *std.Build) !void {
561561 .skip_llvm = skip_llvm,
562562 .skip_libc = true,
563563 .no_builtin = true,
564 .max_rss = switch (b.graph.host.result.os.tag) {
565 .freebsd => switch (b.graph.host.result.cpu.arch) {
566 .x86_64 => 557_892_403,
567 else => 600_000_000,
568 },
569 .linux => switch (b.graph.host.result.cpu.arch) {
570 .aarch64 => 615_302_758,
571 .loongarch64 => 598_974_464,
572 .powerpc64le => 587_845_632,
573 .riscv64 => 382_786_764,
574 .s390x => 395_555_635,
575 .x86_64 => 871_883_161,
576 else => 900_000_000,
577 },
578 .macos => switch (b.graph.host.result.cpu.arch) {
579 .aarch64 => 451_389_030,
580 else => 500_000_000,
581 },
582 .windows => switch (b.graph.host.result.cpu.arch) {
583 .x86_64 => 367_747_072,
584 else => 400_000_000,
585 },
586 else => 900_000_000,
587 },
564 .max_rss = 900_000_000,
588565 }));
589566
590567 test_modules_step.dependOn(tests.addModuleTests(b, .{
......@@ -647,30 +624,7 @@ pub fn build(b: *std.Build) !void {
647624 .use_llvm = use_llvm,
648625 .use_lld = use_llvm,
649626 .zig_lib_dir = b.path("lib"),
650 .max_rss = switch (b.graph.host.result.os.tag) {
651 .freebsd => switch (b.graph.host.result.cpu.arch) {
652 .x86_64 => 2_188_099_584,
653 else => 2_200_000_000,
654 },
655 .linux => switch (b.graph.host.result.cpu.arch) {
656 .aarch64 => 1_991_934_771,
657 .loongarch64 => 1_844_538_572,
658 .powerpc64le => 1_793_035_059,
659 .riscv64 => 2_459_003_289,
660 .s390x => 1_781_248_409,
661 .x86_64 => 977_192_550,
662 else => 2_500_000_000,
663 },
664 .macos => switch (b.graph.host.result.cpu.arch) {
665 .aarch64 => 2_062_393_344,
666 else => 2_100_000_000,
667 },
668 .windows => switch (b.graph.host.result.cpu.arch) {
669 .x86_64 => 1_953_087_488,
670 else => 2_000_000_000,
671 },
672 else => 2_500_000_000,
673 },
627 .max_rss = 2_500_000_000,
674628 });
675629 if (link_libc) {
676630 unit_tests.root_module.link_libc = true;
......@@ -762,7 +716,7 @@ pub fn build(b: *std.Build) !void {
762716 }
763717
764718 const test_incremental_step = b.step("test-incremental", "Run the incremental compilation test cases");
765 try tests.addIncrementalTests(b, test_incremental_step);
719 try tests.addIncrementalTests(b, test_incremental_step, test_filters);
766720 if (!skip_test_incremental) test_step.dependOn(test_incremental_step);
767721
768722 if (tests.addLibcTests(b, .{
......@@ -1153,10 +1107,13 @@ const CMakeConfig = struct {
11531107const max_config_h_bytes = 1 * 1024 * 1024;
11541108
11551109fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
1110 const io = b.graph.io;
1111 const cwd: Io.Dir = .cwd();
1112
11561113 if (config_h_path_option) |path| {
1157 var config_h_or_err = fs.cwd().openFile(path, .{});
1114 var config_h_or_err = cwd.openFile(io, path, .{});
11581115 if (config_h_or_err) |*file| {
1159 file.close();
1116 file.close(io);
11601117 return path;
11611118 } else |_| {
11621119 std.log.err("Could not open provided config.h: \"{s}\"", .{path});
......@@ -1166,13 +1123,13 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
11661123
11671124 var check_dir = fs.path.dirname(b.graph.zig_exe).?;
11681125 while (true) {
1169 var dir = fs.cwd().openDir(check_dir, .{}) catch unreachable;
1170 defer dir.close();
1126 var dir = cwd.openDir(io, check_dir, .{}) catch unreachable;
1127 defer dir.close(io);
11711128
11721129 // Check if config.h is present in dir
1173 var config_h_or_err = dir.openFile("config.h", .{});
1130 var config_h_or_err = dir.openFile(io, "config.h", .{});
11741131 if (config_h_or_err) |*file| {
1175 file.close();
1132 file.close(io);
11761133 return fs.path.join(
11771134 b.allocator,
11781135 &[_][]const u8{ check_dir, "config.h" },
......@@ -1183,9 +1140,9 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
11831140 }
11841141
11851142 // Check if we reached the source root by looking for .git, and bail if so
1186 var git_dir_or_err = dir.openDir(".git", .{});
1143 var git_dir_or_err = dir.openDir(io, ".git", .{});
11871144 if (git_dir_or_err) |*git_dir| {
1188 git_dir.close();
1145 git_dir.close(io);
11891146 return null;
11901147 } else |_| {}
11911148
......@@ -1581,6 +1538,8 @@ const llvm_libs_xtensa = [_][]const u8{
15811538};
15821539
15831540fn generateLangRef(b: *std.Build) std.Build.LazyPath {
1541 const io = b.graph.io;
1542
15841543 const doctest_exe = b.addExecutable(.{
15851544 .name = "doctest",
15861545 .root_module = b.createModule(.{
......@@ -1590,17 +1549,17 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
15901549 }),
15911550 });
15921551
1593 var dir = b.build_root.handle.openDir("doc/langref", .{ .iterate = true }) catch |err| {
1552 var dir = b.build_root.handle.openDir(io, "doc/langref", .{ .iterate = true }) catch |err| {
15941553 std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{
15951554 b.build_root, @errorName(err),
15961555 });
15971556 };
1598 defer dir.close();
1557 defer dir.close(io);
15991558
16001559 var wf = b.addWriteFiles();
16011560
16021561 var it = dir.iterateAssumeFirstIteration();
1603 while (it.next() catch @panic("failed to read dir")) |entry| {
1562 while (it.next(io) catch @panic("failed to read dir")) |entry| {
16041563 if (std.mem.startsWith(u8, entry.name, ".") or entry.kind != .file)
16051564 continue;
16061565
doc/langref/bad_default_value.zig+1-1
......@@ -17,7 +17,7 @@ pub fn main() !void {
1717 .maximum = 0.20,
1818 };
1919 const category = threshold.categorize(0.90);
20 try std.fs.File.stdout().writeAll(@tagName(category));
20 std.log.info("category: {t}", .{category});
2121}
2222
2323const std = @import("std");
doc/langref/hello.zig+11-1
......@@ -1,7 +1,17 @@
11const std = @import("std");
22
3// See https://github.com/ziglang/zig/issues/24510
4// for the plan to simplify this code.
35pub fn main() !void {
4 try std.fs.File.stdout().writeAll("Hello, World!\n");
6 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
7 defer _ = debug_allocator.deinit();
8 const gpa = debug_allocator.allocator();
9
10 var threaded: std.Io.Threaded = .init(gpa, .{});
11 defer threaded.deinit();
12 const io = threaded.io();
13
14 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
515}
616
717// exe=succeed
lib/compiler/aro/aro/Compilation.zig+21-15
......@@ -154,7 +154,7 @@ gpa: Allocator,
154154/// Allocations in this arena live all the way until `Compilation.deinit`.
155155arena: Allocator,
156156io: Io,
157cwd: std.fs.Dir,
157cwd: Io.Dir,
158158diagnostics: *Diagnostics,
159159
160160sources: std.StringArrayHashMapUnmanaged(Source) = .empty,
......@@ -181,7 +181,7 @@ pragma_handlers: std.StringArrayHashMapUnmanaged(*Pragma) = .empty,
181181/// Used by MS extensions which allow searching for includes relative to the directory of the main source file.
182182ms_cwd_source_id: ?Source.Id = null,
183183
184pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation {
184pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: Io.Dir) Compilation {
185185 return .{
186186 .gpa = gpa,
187187 .arena = arena,
......@@ -193,7 +193,7 @@ pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics,
193193
194194/// Initialize Compilation with default environment,
195195/// pragma handlers and emulation mode set to target.
196pub fn initDefault(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: std.fs.Dir) !Compilation {
196pub fn initDefault(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: Io.Dir) !Compilation {
197197 var comp: Compilation = .{
198198 .gpa = gpa,
199199 .arena = arena,
......@@ -1639,12 +1639,14 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin
16391639 return error.FileNotFound;
16401640 }
16411641
1642 const file = try comp.cwd.openFile(path, .{});
1643 defer file.close();
1642 const io = comp.io;
1643
1644 const file = try comp.cwd.openFile(io, path, .{});
1645 defer file.close(io);
16441646 return comp.addSourceFromFile(file, path, kind);
16451647}
16461648
1647pub fn addSourceFromFile(comp: *Compilation, file: std.fs.File, path: []const u8, kind: Source.Kind) !Source {
1649pub fn addSourceFromFile(comp: *Compilation, file: Io.File, path: []const u8, kind: Source.Kind) !Source {
16481650 const contents = try comp.getFileContents(file, .unlimited);
16491651 errdefer comp.gpa.free(contents);
16501652 return comp.addSourceFromOwnedBuffer(path, contents, kind);
......@@ -1711,7 +1713,8 @@ pub fn initSearchPath(comp: *Compilation, includes: []const Include, verbose: bo
17111713 }
17121714}
17131715fn addToSearchPath(comp: *Compilation, include: Include, verbose: bool) !void {
1714 comp.cwd.access(include.path, .{}) catch {
1716 const io = comp.io;
1717 comp.cwd.access(io, include.path, .{}) catch {
17151718 if (verbose) {
17161719 std.debug.print("ignoring nonexistent directory \"{s}\"\n", .{include.path});
17171720 return;
......@@ -1971,12 +1974,14 @@ fn getPathContents(comp: *Compilation, path: []const u8, limit: Io.Limit) ![]u8
19711974 return error.FileNotFound;
19721975 }
19731976
1974 const file = try comp.cwd.openFile(path, .{});
1975 defer file.close();
1977 const io = comp.io;
1978
1979 const file = try comp.cwd.openFile(io, path, .{});
1980 defer file.close(io);
19761981 return comp.getFileContents(file, limit);
19771982}
19781983
1979fn getFileContents(comp: *Compilation, file: std.fs.File, limit: Io.Limit) ![]u8 {
1984fn getFileContents(comp: *Compilation, file: Io.File, limit: Io.Limit) ![]u8 {
19801985 var file_buf: [4096]u8 = undefined;
19811986 var file_reader = file.reader(comp.io, &file_buf);
19821987
......@@ -2158,8 +2163,9 @@ pub fn locSlice(comp: *const Compilation, loc: Source.Location) []const u8 {
21582163}
21592164
21602165pub fn getSourceMTimeUncached(comp: *const Compilation, source_id: Source.Id) ?u64 {
2166 const io = comp.io;
21612167 const source = comp.getSource(source_id);
2162 if (comp.cwd.statFile(source.path)) |stat| {
2168 if (comp.cwd.statFile(io, source.path, .{})) |stat| {
21632169 return std.math.cast(u64, stat.mtime.toSeconds());
21642170 } else |_| {
21652171 return null;
......@@ -2249,7 +2255,7 @@ test "addSourceFromBuffer" {
22492255 var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
22502256 defer arena.deinit();
22512257 var diagnostics: Diagnostics = .{ .output = .ignore };
2252 var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, &diagnostics, std.fs.cwd());
2258 var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, &diagnostics, Io.Dir.cwd());
22532259 defer comp.deinit();
22542260
22552261 const source = try comp.addSourceFromBuffer("path", str);
......@@ -2263,7 +2269,7 @@ test "addSourceFromBuffer" {
22632269 var arena: std.heap.ArenaAllocator = .init(allocator);
22642270 defer arena.deinit();
22652271 var diagnostics: Diagnostics = .{ .output = .ignore };
2266 var comp = Compilation.init(allocator, arena.allocator(), std.testing.io, &diagnostics, std.fs.cwd());
2272 var comp = Compilation.init(allocator, arena.allocator(), std.testing.io, &diagnostics, Io.Dir.cwd());
22672273 defer comp.deinit();
22682274
22692275 _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n");
......@@ -2309,7 +2315,7 @@ test "addSourceFromBuffer - exhaustive check for carriage return elimination" {
23092315 var buf: [alphabet.len]u8 = @splat(alphabet[0]);
23102316
23112317 var diagnostics: Diagnostics = .{ .output = .ignore };
2312 var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, &diagnostics, std.fs.cwd());
2318 var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, &diagnostics, Io.Dir.cwd());
23132319 defer comp.deinit();
23142320
23152321 var source_count: u32 = 0;
......@@ -2337,7 +2343,7 @@ test "ignore BOM at beginning of file" {
23372343 const Test = struct {
23382344 fn run(arena: Allocator, buf: []const u8) !void {
23392345 var diagnostics: Diagnostics = .{ .output = .ignore };
2340 var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, &diagnostics, std.fs.cwd());
2346 var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, &diagnostics, Io.Dir.cwd());
23412347 defer comp.deinit();
23422348
23432349 const source = try comp.addSourceFromBuffer("file.c", buf);
lib/compiler/aro/aro/Diagnostics.zig+18-20
......@@ -24,20 +24,21 @@ pub const Message = struct {
2424 @"fatal error",
2525 };
2626
27 pub fn write(msg: Message, w: *std.Io.Writer, config: std.Io.tty.Config, details: bool) std.Io.tty.Config.SetColorError!void {
28 try config.setColor(w, .bold);
27 pub fn write(msg: Message, t: std.Io.Terminal, details: bool) std.Io.Terminal.SetColorError!void {
28 const w = t.writer;
29 try t.setColor(.bold);
2930 if (msg.location) |loc| {
3031 try w.print("{s}:{d}:{d}: ", .{ loc.path, loc.line_no, loc.col });
3132 }
3233 switch (msg.effective_kind) {
33 .@"fatal error", .@"error" => try config.setColor(w, .bright_red),
34 .note => try config.setColor(w, .bright_cyan),
35 .warning => try config.setColor(w, .bright_magenta),
34 .@"fatal error", .@"error" => try t.setColor(.bright_red),
35 .note => try t.setColor(.bright_cyan),
36 .warning => try t.setColor(.bright_magenta),
3637 .off => unreachable,
3738 }
3839 try w.print("{s}: ", .{@tagName(msg.effective_kind)});
3940
40 try config.setColor(w, .white);
41 try t.setColor(.white);
4142 try w.writeAll(msg.text);
4243 if (msg.opt) |some| {
4344 if (msg.effective_kind == .@"error" and msg.kind != .@"error") {
......@@ -55,17 +56,17 @@ pub const Message = struct {
5556
5657 if (!details or msg.location == null) {
5758 try w.writeAll("\n");
58 try config.setColor(w, .reset);
59 try t.setColor(.reset);
5960 } else {
6061 const loc = msg.location.?;
6162 const trailer = if (loc.end_with_splice) "\\ " else "";
62 try config.setColor(w, .reset);
63 try t.setColor(.reset);
6364 try w.print("\n{s}{s}\n", .{ loc.line, trailer });
6465 try w.splatByteAll(' ', loc.width);
65 try config.setColor(w, .bold);
66 try config.setColor(w, .bright_green);
66 try t.setColor(.bold);
67 try t.setColor(.bright_green);
6768 try w.writeAll("^\n");
68 try config.setColor(w, .reset);
69 try t.setColor(.reset);
6970 }
7071 try w.flush();
7172 }
......@@ -290,10 +291,7 @@ pub const State = struct {
290291const Diagnostics = @This();
291292
292293output: union(enum) {
293 to_writer: struct {
294 writer: *std.Io.Writer,
295 color: std.Io.tty.Config,
296 },
294 to_writer: std.Io.Terminal,
297295 to_list: struct {
298296 messages: std.ArrayList(Message) = .empty,
299297 arena: std.heap.ArenaAllocator,
......@@ -543,11 +541,11 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void {
543541
544542 switch (d.output) {
545543 .ignore => {},
546 .to_writer => |writer| {
547 var config = writer.color;
548 if (d.color == false) config = .no_color;
549 if (d.color == true and config == .no_color) config = .escape_codes;
550 msg.write(writer.writer, config, d.details) catch {
544 .to_writer => |t| {
545 var new_mode = t.mode;
546 if (d.color == false) new_mode = .no_color;
547 if (d.color == true and new_mode == .no_color) new_mode = .escape_codes;
548 msg.write(.{ .writer = t.writer, .mode = new_mode }, d.details) catch {
551549 return error.FatalError;
552550 };
553551 },
lib/compiler/aro/aro/Driver.zig+25-21
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const mem = std.mem;
34const Allocator = mem.Allocator;
45const process = std.process;
......@@ -133,8 +134,9 @@ strip: bool = false,
133134unwindlib: ?[]const u8 = null,
134135
135136pub fn deinit(d: *Driver) void {
137 const io = d.comp.io;
136138 for (d.link_objects.items[d.link_objects.items.len - d.temp_file_count ..]) |obj| {
137 std.fs.deleteFileAbsolute(obj) catch {};
139 Io.Dir.deleteFileAbsolute(io, obj) catch {};
138140 d.comp.gpa.free(obj);
139141 }
140142 d.inputs.deinit(d.comp.gpa);
......@@ -1061,7 +1063,7 @@ pub fn printDiagnosticsStats(d: *Driver) void {
10611063 }
10621064}
10631065
1064pub fn detectConfig(d: *Driver, file: std.fs.File) std.Io.tty.Config {
1066pub fn detectConfig(d: *Driver, file: Io.File) std.Io.tty.Config {
10651067 if (d.diagnostics.color == false) return .no_color;
10661068 const force_color = d.diagnostics.color == true;
10671069
......@@ -1109,7 +1111,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_
11091111 defer macro_buf.deinit(d.comp.gpa);
11101112
11111113 var stdout_buf: [256]u8 = undefined;
1112 var stdout = std.fs.File.stdout().writer(&stdout_buf);
1114 var stdout = Io.File.stdout().writer(&stdout_buf);
11131115 if (parseArgs(d, &stdout.interface, &macro_buf, args) catch |er| switch (er) {
11141116 error.WriteFailed => return d.fatal("failed to write to stdout: {s}", .{errorDescription(er)}),
11151117 error.OutOfMemory => return error.OutOfMemory,
......@@ -1286,6 +1288,8 @@ fn processSource(
12861288 d.comp.generated_buf.items.len = 0;
12871289 const prev_total = d.diagnostics.errors;
12881290
1291 const io = d.comp.io;
1292
12891293 var pp = try Preprocessor.initDefault(d.comp);
12901294 defer pp.deinit();
12911295
......@@ -1324,13 +1328,13 @@ fn processSource(
13241328 const dep_file_name = try d.getDepFileName(source, writer_buf[0..std.fs.max_name_bytes]);
13251329
13261330 const file = if (dep_file_name) |path|
1327 d.comp.cwd.createFile(path, .{}) catch |er|
1331 d.comp.cwd.createFile(io, path, .{}) catch |er|
13281332 return d.fatal("unable to create dependency file '{s}': {s}", .{ path, errorDescription(er) })
13291333 else
1330 std.fs.File.stdout();
1331 defer if (dep_file_name != null) file.close();
1334 Io.File.stdout();
1335 defer if (dep_file_name != null) file.close(io);
13321336
1333 var file_writer = file.writer(&writer_buf);
1337 var file_writer = file.writer(io, &writer_buf);
13341338 dep_file.write(&file_writer.interface) catch
13351339 return d.fatal("unable to write dependency file: {s}", .{errorDescription(file_writer.err.?)});
13361340 }
......@@ -1349,13 +1353,13 @@ fn processSource(
13491353 }
13501354
13511355 const file = if (d.output_name) |some|
1352 d.comp.cwd.createFile(some, .{}) catch |er|
1356 d.comp.cwd.createFile(io, some, .{}) catch |er|
13531357 return d.fatal("unable to create output file '{s}': {s}", .{ some, errorDescription(er) })
13541358 else
1355 std.fs.File.stdout();
1356 defer if (d.output_name != null) file.close();
1359 Io.File.stdout();
1360 defer if (d.output_name != null) file.close(io);
13571361
1358 var file_writer = file.writer(&writer_buf);
1362 var file_writer = file.writer(io, &writer_buf);
13591363 pp.prettyPrintTokens(&file_writer.interface, dump_mode) catch
13601364 return d.fatal("unable to write result: {s}", .{errorDescription(file_writer.err.?)});
13611365
......@@ -1367,7 +1371,7 @@ fn processSource(
13671371 defer tree.deinit();
13681372
13691373 if (d.verbose_ast) {
1370 var stdout = std.fs.File.stdout().writer(&writer_buf);
1374 var stdout = Io.File.stdout().writer(&writer_buf);
13711375 tree.dump(d.detectConfig(stdout.file), &stdout.interface) catch {};
13721376 }
13731377
......@@ -1402,9 +1406,9 @@ fn processSource(
14021406 defer assembly.deinit(gpa);
14031407
14041408 if (d.only_preprocess_and_compile) {
1405 const out_file = d.comp.cwd.createFile(out_file_name, .{}) catch |er|
1409 const out_file = d.comp.cwd.createFile(io, out_file_name, .{}) catch |er|
14061410 return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) });
1407 defer out_file.close();
1411 defer out_file.close(io);
14081412
14091413 assembly.writeToFile(out_file) catch |er|
14101414 return d.fatal("unable to write to output file '{s}': {s}", .{ out_file_name, errorDescription(er) });
......@@ -1416,9 +1420,9 @@ fn processSource(
14161420 // then assemble to out_file_name
14171421 var assembly_name_buf: [std.fs.max_name_bytes]u8 = undefined;
14181422 const assembly_out_file_name = try d.getRandomFilename(&assembly_name_buf, ".s");
1419 const out_file = d.comp.cwd.createFile(assembly_out_file_name, .{}) catch |er|
1423 const out_file = d.comp.cwd.createFile(io, assembly_out_file_name, .{}) catch |er|
14201424 return d.fatal("unable to create output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) });
1421 defer out_file.close();
1425 defer out_file.close(io);
14221426 assembly.writeToFile(out_file) catch |er|
14231427 return d.fatal("unable to write to output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) });
14241428 try d.invokeAssembler(tc, assembly_out_file_name, out_file_name);
......@@ -1431,7 +1435,7 @@ fn processSource(
14311435 defer ir.deinit(gpa);
14321436
14331437 if (d.verbose_ir) {
1434 var stdout = std.fs.File.stdout().writer(&writer_buf);
1438 var stdout = Io.File.stdout().writer(&writer_buf);
14351439 ir.dump(gpa, d.detectConfig(stdout.file), &stdout.interface) catch {};
14361440 }
14371441
......@@ -1452,11 +1456,11 @@ fn processSource(
14521456 };
14531457 defer obj.deinit();
14541458
1455 const out_file = d.comp.cwd.createFile(out_file_name, .{}) catch |er|
1459 const out_file = d.comp.cwd.createFile(io, out_file_name, .{}) catch |er|
14561460 return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) });
1457 defer out_file.close();
1461 defer out_file.close(io);
14581462
1459 var file_writer = out_file.writer(&writer_buf);
1463 var file_writer = out_file.writer(io, &writer_buf);
14601464 obj.finish(&file_writer.interface) catch
14611465 return d.fatal("could not output to object file '{s}': {s}", .{ out_file_name, errorDescription(file_writer.err.?) });
14621466 }
......@@ -1497,7 +1501,7 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compil
14971501
14981502 if (d.verbose_linker_args) {
14991503 var stdout_buf: [4096]u8 = undefined;
1500 var stdout = std.fs.File.stdout().writer(&stdout_buf);
1504 var stdout = Io.File.stdout().writer(&stdout_buf);
15011505 dumpLinkerArgs(&stdout.interface, argv.items) catch {
15021506 return d.fatal("unable to dump linker args: {s}", .{errorDescription(stdout.err.?)});
15031507 };
lib/compiler/aro/aro/Driver/Filesystem.zig+21-19
......@@ -1,8 +1,10 @@
1const std = @import("std");
2const mem = std.mem;
31const builtin = @import("builtin");
42const is_windows = builtin.os.tag == .windows;
53
4const std = @import("std");
5const Io = std.Io;
6const mem = std.std.mem;
7
68fn readFileFake(entries: []const Filesystem.Entry, path: []const u8, buf: []u8) ?[]const u8 {
79 @branchHint(.cold);
810 for (entries) |entry| {
......@@ -55,8 +57,8 @@ fn existsFake(entries: []const Filesystem.Entry, path: []const u8) bool {
5557 return false;
5658}
5759
58fn canExecutePosix(path: []const u8) bool {
59 std.posix.access(path, std.posix.X_OK) catch return false;
60fn canExecutePosix(io: Io, path: []const u8) bool {
61 Io.Dir.accessAbsolute(io, path, .{ .execute = true }) catch return false;
6062 // Todo: ensure path is not a directory
6163 return true;
6264}
......@@ -96,7 +98,7 @@ fn findProgramByNamePosix(name: []const u8, path: ?[]const u8, buf: []u8) ?[]con
9698}
9799
98100pub const Filesystem = union(enum) {
99 real: std.fs.Dir,
101 real: std.Io.Dir,
100102 fake: []const Entry,
101103
102104 const Entry = struct {
......@@ -121,7 +123,7 @@ pub const Filesystem = union(enum) {
121123 base: []const u8,
122124 i: usize = 0,
123125
124 fn next(self: *@This()) !?std.fs.Dir.Entry {
126 fn next(self: *@This()) !?std.Io.Dir.Entry {
125127 while (self.i < self.entries.len) {
126128 const entry = self.entries[self.i];
127129 self.i += 1;
......@@ -130,7 +132,7 @@ pub const Filesystem = union(enum) {
130132 const remaining = entry.path[self.base.len + 1 ..];
131133 if (std.mem.indexOfScalar(u8, remaining, std.fs.path.sep) != null) continue;
132134 const extension = std.fs.path.extension(remaining);
133 const kind: std.fs.Dir.Entry.Kind = if (extension.len == 0) .directory else .file;
135 const kind: std.Io.Dir.Entry.Kind = if (extension.len == 0) .directory else .file;
134136 return .{ .name = remaining, .kind = kind };
135137 }
136138 }
......@@ -140,7 +142,7 @@ pub const Filesystem = union(enum) {
140142 };
141143
142144 const Dir = union(enum) {
143 dir: std.fs.Dir,
145 dir: std.Io.Dir,
144146 fake: FakeDir,
145147
146148 pub fn iterate(self: Dir) Iterator {
......@@ -150,19 +152,19 @@ pub const Filesystem = union(enum) {
150152 };
151153 }
152154
153 pub fn close(self: *Dir) void {
155 pub fn close(self: *Dir, io: Io) void {
154156 switch (self.*) {
155 .dir => |*d| d.close(),
157 .dir => |*d| d.close(io),
156158 .fake => {},
157159 }
158160 }
159161 };
160162
161163 const Iterator = union(enum) {
162 iterator: std.fs.Dir.Iterator,
164 iterator: std.Io.Dir.Iterator,
163165 fake: FakeDir.Iterator,
164166
165 pub fn next(self: *Iterator) std.fs.Dir.Iterator.Error!?std.fs.Dir.Entry {
167 pub fn next(self: *Iterator) std.Io.Dir.Iterator.Error!?std.Io.Dir.Entry {
166168 return switch (self.*) {
167169 .iterator => |*it| it.next(),
168170 .fake => |*it| it.next(),
......@@ -170,10 +172,10 @@ pub const Filesystem = union(enum) {
170172 }
171173 };
172174
173 pub fn exists(fs: Filesystem, path: []const u8) bool {
175 pub fn exists(fs: Filesystem, io: Io, path: []const u8) bool {
174176 switch (fs) {
175177 .real => |cwd| {
176 cwd.access(path, .{}) catch return false;
178 cwd.access(io, path, .{}) catch return false;
177179 return true;
178180 },
179181 .fake => |paths| return existsFake(paths, path),
......@@ -208,11 +210,11 @@ pub const Filesystem = union(enum) {
208210 /// Read the file at `path` into `buf`.
209211 /// Returns null if any errors are encountered
210212 /// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned
211 pub fn readFile(fs: Filesystem, path: []const u8, buf: []u8) ?[]const u8 {
213 pub fn readFile(fs: Filesystem, io: Io, path: []const u8, buf: []u8) ?[]const u8 {
212214 return switch (fs) {
213215 .real => |cwd| {
214 const file = cwd.openFile(path, .{}) catch return null;
215 defer file.close();
216 const file = cwd.openFile(io, path, .{}) catch return null;
217 defer file.close(io);
216218
217219 const bytes_read = file.readAll(buf) catch return null;
218220 return buf[0..bytes_read];
......@@ -221,9 +223,9 @@ pub const Filesystem = union(enum) {
221223 };
222224 }
223225
224 pub fn openDir(fs: Filesystem, dir_name: []const u8) std.fs.Dir.OpenError!Dir {
226 pub fn openDir(fs: Filesystem, io: Io, dir_name: []const u8) std.Io.Dir.OpenError!Dir {
225227 return switch (fs) {
226 .real => |cwd| .{ .dir = try cwd.openDir(dir_name, .{ .access_sub_paths = false, .iterate = true }) },
228 .real => |cwd| .{ .dir = try cwd.openDir(io, dir_name, .{ .access_sub_paths = false, .iterate = true }) },
227229 .fake => |entries| .{ .fake = .{ .entries = entries, .path = dir_name } },
228230 };
229231 }
lib/compiler/aro/aro/Parser.zig+14-13
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const mem = std.mem;
34const Allocator = mem.Allocator;
45const assert = std.debug.assert;
......@@ -211,7 +212,7 @@ fn checkIdentifierCodepointWarnings(p: *Parser, codepoint: u21, loc: Source.Loca
211212
212213 const prev_total = p.diagnostics.total;
213214 var sf = std.heap.stackFallback(1024, p.comp.gpa);
214 var allocating: std.Io.Writer.Allocating = .init(sf.get());
215 var allocating: Io.Writer.Allocating = .init(sf.get());
215216 defer allocating.deinit();
216217
217218 if (!char_info.isC99IdChar(codepoint)) {
......@@ -425,7 +426,7 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype)
425426 if (p.diagnostics.effectiveKind(diagnostic) == .off) return;
426427
427428 var sf = std.heap.stackFallback(1024, p.comp.gpa);
428 var allocating: std.Io.Writer.Allocating = .init(sf.get());
429 var allocating: Io.Writer.Allocating = .init(sf.get());
429430 defer allocating.deinit();
430431
431432 p.formatArgs(&allocating.writer, diagnostic.fmt, args) catch return error.OutOfMemory;
......@@ -447,7 +448,7 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype)
447448 }, p.pp.expansionSlice(tok_i), true);
448449}
449450
450fn formatArgs(p: *Parser, w: *std.Io.Writer, fmt: []const u8, args: anytype) !void {
451fn formatArgs(p: *Parser, w: *Io.Writer, fmt: []const u8, args: anytype) !void {
451452 var i: usize = 0;
452453 inline for (std.meta.fields(@TypeOf(args))) |arg_info| {
453454 const arg = @field(args, arg_info.name);
......@@ -476,13 +477,13 @@ fn formatArgs(p: *Parser, w: *std.Io.Writer, fmt: []const u8, args: anytype) !vo
476477 try w.writeAll(fmt[i..]);
477478}
478479
479fn formatTokenId(w: *std.Io.Writer, fmt: []const u8, tok_id: Tree.Token.Id) !usize {
480fn formatTokenId(w: *Io.Writer, fmt: []const u8, tok_id: Tree.Token.Id) !usize {
480481 const i = Diagnostics.templateIndex(w, fmt, "{tok_id}");
481482 try w.writeAll(tok_id.symbol());
482483 return i;
483484}
484485
485fn formatQualType(p: *Parser, w: *std.Io.Writer, fmt: []const u8, qt: QualType) !usize {
486fn formatQualType(p: *Parser, w: *Io.Writer, fmt: []const u8, qt: QualType) !usize {
486487 const i = Diagnostics.templateIndex(w, fmt, "{qt}");
487488 try w.writeByte('\'');
488489 try qt.print(p.comp, w);
......@@ -501,7 +502,7 @@ fn formatQualType(p: *Parser, w: *std.Io.Writer, fmt: []const u8, qt: QualType)
501502 return i;
502503}
503504
504fn formatResult(p: *Parser, w: *std.Io.Writer, fmt: []const u8, res: Result) !usize {
505fn formatResult(p: *Parser, w: *Io.Writer, fmt: []const u8, res: Result) !usize {
505506 const i = Diagnostics.templateIndex(w, fmt, "{value}");
506507 switch (res.val.opt_ref) {
507508 .none => try w.writeAll("(none)"),
......@@ -524,7 +525,7 @@ const Normalized = struct {
524525 return .{ .str = str };
525526 }
526527
527 pub fn format(ctx: Normalized, w: *std.Io.Writer, fmt: []const u8) !usize {
528 pub fn format(ctx: Normalized, w: *Io.Writer, fmt: []const u8) !usize {
528529 const i = Diagnostics.templateIndex(w, fmt, "{normalized}");
529530 var it: std.unicode.Utf8Iterator = .{
530531 .bytes = ctx.str,
......@@ -558,7 +559,7 @@ const Codepoint = struct {
558559 return .{ .codepoint = codepoint };
559560 }
560561
561 pub fn format(ctx: Codepoint, w: *std.Io.Writer, fmt: []const u8) !usize {
562 pub fn format(ctx: Codepoint, w: *Io.Writer, fmt: []const u8) !usize {
562563 const i = Diagnostics.templateIndex(w, fmt, "{codepoint}");
563564 try w.print("{X:0>4}", .{ctx.codepoint});
564565 return i;
......@@ -572,7 +573,7 @@ const Escaped = struct {
572573 return .{ .str = str };
573574 }
574575
575 pub fn format(ctx: Escaped, w: *std.Io.Writer, fmt: []const u8) !usize {
576 pub fn format(ctx: Escaped, w: *Io.Writer, fmt: []const u8) !usize {
576577 const i = Diagnostics.templateIndex(w, fmt, "{s}");
577578 try std.zig.stringEscape(ctx.str, w);
578579 return i;
......@@ -1453,7 +1454,7 @@ fn decl(p: *Parser) Error!bool {
14531454 return true;
14541455}
14551456
1456fn staticAssertMessage(p: *Parser, cond_node: Node.Index, maybe_message: ?Result, allocating: *std.Io.Writer.Allocating) !?[]const u8 {
1457fn staticAssertMessage(p: *Parser, cond_node: Node.Index, maybe_message: ?Result, allocating: *Io.Writer.Allocating) !?[]const u8 {
14571458 const w = &allocating.writer;
14581459
14591460 const cond = cond_node.get(&p.tree);
......@@ -1526,7 +1527,7 @@ fn staticAssert(p: *Parser) Error!bool {
15261527 } else {
15271528 if (!res.val.toBool(p.comp)) {
15281529 var sf = std.heap.stackFallback(1024, gpa);
1529 var allocating: std.Io.Writer.Allocating = .init(sf.get());
1530 var allocating: Io.Writer.Allocating = .init(sf.get());
15301531 defer allocating.deinit();
15311532
15321533 if (p.staticAssertMessage(res_node, str, &allocating) catch return error.OutOfMemory) |message| {
......@@ -9719,7 +9720,7 @@ fn primaryExpr(p: *Parser) Error!?Result {
97199720 qt = some.qt;
97209721 } else if (p.func.qt) |func_qt| {
97219722 var sf = std.heap.stackFallback(1024, gpa);
9722 var allocating: std.Io.Writer.Allocating = .init(sf.get());
9723 var allocating: Io.Writer.Allocating = .init(sf.get());
97239724 defer allocating.deinit();
97249725
97259726 func_qt.printNamed(p.tokSlice(p.func.name), p.comp, &allocating.writer) catch return error.OutOfMemory;
......@@ -10608,7 +10609,7 @@ test "Node locations" {
1060810609 const arena = arena_state.allocator();
1060910610
1061010611 var diagnostics: Diagnostics = .{ .output = .ignore };
10611 var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, &diagnostics, std.fs.cwd());
10612 var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, &diagnostics, Io.Dir.cwd());
1061210613 defer comp.deinit();
1061310614
1061410615 const file = try comp.addSourceFromBuffer("file.c",
lib/compiler/aro/aro/Preprocessor.zig+8-5
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const mem = std.mem;
34const Allocator = mem.Allocator;
45const assert = std.debug.assert;
......@@ -1064,11 +1065,13 @@ fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []con
10641065
10651066fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) void {
10661067 @branchHint(.cold);
1067 const source = pp.comp.getSource(raw.source);
1068 const comp = pp.comp;
1069 const io = comp.io;
1070 const source = comp.getSource(raw.source);
10681071 const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start });
10691072
10701073 var stderr_buf: [4096]u8 = undefined;
1071 var stderr = std.fs.File.stderr().writer(&stderr_buf);
1074 var stderr = Io.File.stderr().writer(io, &stderr_buf);
10721075 const w = &stderr.interface;
10731076
10741077 w.print("{s}:{d}:{d}: ", .{ source.path, line_col.line_no, line_col.col }) catch return;
......@@ -3899,7 +3902,7 @@ test "Preserve pragma tokens sometimes" {
38993902 defer arena.deinit();
39003903
39013904 var diagnostics: Diagnostics = .{ .output = .ignore };
3902 var comp = Compilation.init(gpa, arena.allocator(), std.testing.io, &diagnostics, std.fs.cwd());
3905 var comp = Compilation.init(gpa, arena.allocator(), std.testing.io, &diagnostics, Io.Dir.cwd());
39033906 defer comp.deinit();
39043907
39053908 try comp.addDefaultPragmaHandlers();
......@@ -3966,7 +3969,7 @@ test "destringify" {
39663969 var arena: std.heap.ArenaAllocator = .init(gpa);
39673970 defer arena.deinit();
39683971 var diagnostics: Diagnostics = .{ .output = .ignore };
3969 var comp = Compilation.init(gpa, arena.allocator(), std.testing.io, &diagnostics, std.fs.cwd());
3972 var comp = Compilation.init(gpa, arena.allocator(), std.testing.io, &diagnostics, Io.Dir.cwd());
39703973 defer comp.deinit();
39713974 var pp = Preprocessor.init(&comp, .default);
39723975 defer pp.deinit();
......@@ -4029,7 +4032,7 @@ test "Include guards" {
40294032 const arena = arena_state.allocator();
40304033
40314034 var diagnostics: Diagnostics = .{ .output = .ignore };
4032 var comp = Compilation.init(gpa, arena, std.testing.io, &diagnostics, std.fs.cwd());
4035 var comp = Compilation.init(gpa, arena, std.testing.io, &diagnostics, Io.Dir.cwd());
40334036 defer comp.deinit();
40344037 var pp = Preprocessor.init(&comp, .default);
40354038 defer pp.deinit();
lib/compiler/aro/aro/Tokenizer.zig+3-2
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const assert = std.debug.assert;
34
45const Compilation = @import("Compilation.zig");
......@@ -2326,7 +2327,7 @@ test "Tokenizer fuzz test" {
23262327 fn testOne(_: @This(), input_bytes: []const u8) anyerror!void {
23272328 var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
23282329 defer arena.deinit();
2329 var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, undefined, std.fs.cwd());
2330 var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, undefined, Io.Dir.cwd());
23302331 defer comp.deinit();
23312332
23322333 const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes);
......@@ -2351,7 +2352,7 @@ test "Tokenizer fuzz test" {
23512352fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, langopts: ?LangOpts) !void {
23522353 var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
23532354 defer arena.deinit();
2354 var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, undefined, std.fs.cwd());
2355 var comp = Compilation.init(std.testing.allocator, arena.allocator(), std.testing.io, undefined, Io.Dir.cwd());
23552356 defer comp.deinit();
23562357 if (langopts) |provided| {
23572358 comp.langopts = provided;
lib/compiler/aro/aro/Toolchain.zig+11-7
......@@ -497,10 +497,11 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void {
497497 const comp = d.comp;
498498 const gpa = comp.gpa;
499499 const arena = comp.arena;
500 const io = comp.io;
500501 try d.includes.ensureUnusedCapacity(gpa, 1);
501502 if (d.resource_dir) |resource_dir| {
502503 const path = try std.fs.path.join(arena, &.{ resource_dir, "include" });
503 comp.cwd.access(path, .{}) catch {
504 comp.cwd.access(io, path, .{}) catch {
504505 return d.fatal("Aro builtin headers not found in provided -resource-dir", .{});
505506 };
506507 d.includes.appendAssumeCapacity(.{ .kind = .system, .path = path });
......@@ -508,10 +509,10 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void {
508509 }
509510 var search_path = d.aro_name;
510511 while (std.fs.path.dirname(search_path)) |dirname| : (search_path = dirname) {
511 var base_dir = d.comp.cwd.openDir(dirname, .{}) catch continue;
512 defer base_dir.close();
512 var base_dir = d.comp.cwd.openDir(io, dirname, .{}) catch continue;
513 defer base_dir.close(io);
513514
514 base_dir.access("include/stddef.h", .{}) catch continue;
515 base_dir.access(io, "include/stddef.h", .{}) catch continue;
515516 const path = try std.fs.path.join(arena, &.{ dirname, "include" });
516517 d.includes.appendAssumeCapacity(.{ .kind = .system, .path = path });
517518 break;
......@@ -523,12 +524,14 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void {
523524/// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned
524525pub fn readFile(tc: *const Toolchain, path: []const u8, buf: []u8) ?[]const u8 {
525526 const comp = tc.driver.comp;
526 return comp.cwd.adaptToNewApi().readFile(comp.io, path, buf) catch null;
527 const io = comp.io;
528 return comp.cwd.readFile(io, path, buf) catch null;
527529}
528530
529531pub fn exists(tc: *const Toolchain, path: []const u8) bool {
530532 const comp = tc.driver.comp;
531 comp.cwd.adaptToNewApi().access(comp.io, path, .{}) catch return false;
533 const io = comp.io;
534 comp.cwd.access(io, path, .{}) catch return false;
532535 return true;
533536}
534537
......@@ -546,7 +549,8 @@ pub fn canExecute(tc: *const Toolchain, path: []const u8) bool {
546549 }
547550
548551 const comp = tc.driver.comp;
549 comp.cwd.adaptToNewApi().access(comp.io, path, .{ .execute = true }) catch return false;
552 const io = comp.io;
553 comp.cwd.access(io, path, .{ .execute = true }) catch return false;
550554 // Todo: ensure path is not a directory
551555 return true;
552556}
lib/compiler/aro/aro/Value.zig+6-5
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const assert = std.debug.assert;
34const BigIntConst = std.math.big.int.Const;
45const BigIntMutable = std.math.big.int.Mutable;
......@@ -80,7 +81,7 @@ test "minUnsignedBits" {
8081 defer arena_state.deinit();
8182 const arena = arena_state.allocator();
8283
83 var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, undefined, std.fs.cwd());
84 var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, undefined, Io.Dir.cwd());
8485 defer comp.deinit();
8586 const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" });
8687 comp.target = .fromZigTarget(try std.zig.system.resolveTargetQuery(std.testing.io, target_query));
......@@ -119,7 +120,7 @@ test "minSignedBits" {
119120 defer arena_state.deinit();
120121 const arena = arena_state.allocator();
121122
122 var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, undefined, std.fs.cwd());
123 var comp = Compilation.init(std.testing.allocator, arena, std.testing.io, undefined, Io.Dir.cwd());
123124 defer comp.deinit();
124125 const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" });
125126 comp.target = .fromZigTarget(try std.zig.system.resolveTargetQuery(std.testing.io, target_query));
......@@ -1080,7 +1081,7 @@ const NestedPrint = union(enum) {
10801081 },
10811082};
10821083
1083pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!void {
1084pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w: *Io.Writer) Io.Writer.Error!void {
10841085 try w.writeByte('&');
10851086 try w.writeAll(base);
10861087 if (!offset.isZero(comp)) {
......@@ -1089,7 +1090,7 @@ pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w
10891090 }
10901091}
10911092
1092pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!?NestedPrint {
1093pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *Io.Writer) Io.Writer.Error!?NestedPrint {
10931094 if (qt.is(comp, .bool)) {
10941095 try w.writeAll(if (v.isZero(comp)) "false" else "true");
10951096 return null;
......@@ -1116,7 +1117,7 @@ pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *std.Io.Writer
11161117 return null;
11171118}
11181119
1119pub fn printString(bytes: []const u8, qt: QualType, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!void {
1120pub fn printString(bytes: []const u8, qt: QualType, comp: *const Compilation, w: *Io.Writer) Io.Writer.Error!void {
11201121 const size: Compilation.CharUnitSize = @enumFromInt(qt.childType(comp).sizeof(comp));
11211122 const without_null = bytes[0 .. bytes.len - @intFromEnum(size)];
11221123 try w.writeByte('"');
lib/compiler/aro/backend/Assembly.zig+3-2
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const Allocator = std.mem.Allocator;
34
45data: []const u8,
......@@ -11,8 +12,8 @@ pub fn deinit(self: *const Assembly, gpa: Allocator) void {
1112 gpa.free(self.text);
1213}
1314
14pub fn writeToFile(self: Assembly, file: std.fs.File) !void {
15 var file_writer = file.writer(&.{});
15pub fn writeToFile(self: Assembly, io: Io, file: Io.File) !void {
16 var file_writer = file.writer(io, &.{});
1617
1718 var buffers = [_][]const u8{ self.data, self.text };
1819 try file_writer.interface.writeSplatAll(&buffers, 1);
lib/compiler/aro/main.zig+5-4
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const Allocator = mem.Allocator;
34const mem = std.mem;
45const process = std.process;
......@@ -30,7 +31,7 @@ pub fn main() u8 {
3031 defer arena_instance.deinit();
3132 const arena = arena_instance.allocator();
3233
33 var threaded: std.Io.Threaded = .init(gpa);
34 var threaded: std.Io.Threaded = .init(gpa, .{});
3435 defer threaded.deinit();
3536 const io = threaded.io();
3637
......@@ -42,7 +43,7 @@ pub fn main() u8 {
4243 return 1;
4344 };
4445
45 const aro_name = std.fs.selfExePathAlloc(gpa) catch {
46 const aro_name = process.executablePathAlloc(io, gpa) catch {
4647 std.debug.print("unable to find Aro executable path\n", .{});
4748 if (fast_exit) process.exit(1);
4849 return 1;
......@@ -50,7 +51,7 @@ pub fn main() u8 {
5051 defer gpa.free(aro_name);
5152
5253 var stderr_buf: [1024]u8 = undefined;
53 var stderr = std.fs.File.stderr().writer(&stderr_buf);
54 var stderr = Io.File.stderr().writer(&stderr_buf);
5455 var diagnostics: Diagnostics = .{
5556 .output = .{ .to_writer = .{
5657 .color = .detect(stderr.file),
......@@ -58,7 +59,7 @@ pub fn main() u8 {
5859 } },
5960 };
6061
61 var comp = Compilation.initDefault(gpa, arena, io, &diagnostics, std.fs.cwd()) catch |er| switch (er) {
62 var comp = Compilation.initDefault(gpa, arena, io, &diagnostics, Io.Dir.cwd()) catch |er| switch (er) {
6263 error.OutOfMemory => {
6364 std.debug.print("out of memory\n", .{});
6465 if (fast_exit) process.exit(1);
lib/compiler/build_runner.zig+195-198
......@@ -7,14 +7,13 @@ const assert = std.debug.assert;
77const fmt = std.fmt;
88const mem = std.mem;
99const process = std.process;
10const File = std.fs.File;
10const File = std.Io.File;
1111const Step = std.Build.Step;
1212const Watch = std.Build.Watch;
1313const WebServer = std.Build.WebServer;
1414const Allocator = std.mem.Allocator;
1515const fatal = std.process.fatal;
1616const Writer = std.Io.Writer;
17const tty = std.Io.tty;
1817
1918pub const root = @import("@build");
2019pub const dependencies = @import("@dependencies");
......@@ -40,7 +39,7 @@ pub fn main() !void {
4039
4140 const args = try process.argsAlloc(arena);
4241
43 var threaded: std.Io.Threaded = .init(gpa);
42 var threaded: std.Io.Threaded = .init(gpa, .{});
4443 defer threaded.deinit();
4544 const io = threaded.io();
4645
......@@ -53,24 +52,26 @@ pub fn main() !void {
5352 const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{});
5453 const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{});
5554
55 const cwd: Io.Dir = .cwd();
56
5657 const zig_lib_directory: std.Build.Cache.Directory = .{
5758 .path = zig_lib_dir,
58 .handle = try std.fs.cwd().openDir(zig_lib_dir, .{}),
59 .handle = try cwd.openDir(io, zig_lib_dir, .{}),
5960 };
6061
6162 const build_root_directory: std.Build.Cache.Directory = .{
6263 .path = build_root,
63 .handle = try std.fs.cwd().openDir(build_root, .{}),
64 .handle = try cwd.openDir(io, build_root, .{}),
6465 };
6566
6667 const local_cache_directory: std.Build.Cache.Directory = .{
6768 .path = cache_root,
68 .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}),
69 .handle = try cwd.createDirPathOpen(io, cache_root, .{}),
6970 };
7071
7172 const global_cache_directory: std.Build.Cache.Directory = .{
7273 .path = global_cache_root,
73 .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),
74 .handle = try cwd.createDirPathOpen(io, global_cache_root, .{}),
7475 };
7576
7677 var graph: std.Build.Graph = .{
......@@ -79,7 +80,7 @@ pub fn main() !void {
7980 .cache = .{
8081 .io = io,
8182 .gpa = arena,
82 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
83 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),
8384 },
8485 .zig_exe = zig_exe,
8586 .env_map = try process.getEnvMap(arena),
......@@ -92,7 +93,7 @@ pub fn main() !void {
9293 .time_report = false,
9394 };
9495
95 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
96 graph.cache.addPrefix(.{ .path = null, .handle = cwd });
9697 graph.cache.addPrefix(build_root_directory);
9798 graph.cache.addPrefix(local_cache_directory);
9899 graph.cache.addPrefix(global_cache_directory);
......@@ -285,8 +286,8 @@ pub fn main() !void {
285286 const next_arg = nextArg(args, &arg_idx) orelse
286287 fatalWithHint("expected u16 after '{s}'", .{arg});
287288 debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| {
288 fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {s}\n", .{
289 next_arg, @errorName(err),
289 fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {t}\n", .{
290 next_arg, err,
290291 });
291292 };
292293 } else if (mem.eql(u8, arg, "--webui")) {
......@@ -428,14 +429,21 @@ pub fn main() !void {
428429 }
429430 }
430431
432 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet();
433 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet();
434
435 graph.stderr_mode = switch (color) {
436 .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),
437 .on => .escape_codes,
438 .off => .no_color,
439 };
440
431441 if (webui_listen != null) {
432442 if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{});
433443 if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{});
434444 }
435445
436 const ttyconf = color.detectTtyConf();
437
438 const main_progress_node = std.Progress.start(.{
446 const main_progress_node = std.Progress.start(io, .{
439447 .disable_printing = (color == .off),
440448 });
441449 defer main_progress_node.end();
......@@ -457,7 +465,7 @@ pub fn main() !void {
457465 }
458466 const s = std.fs.path.sep_str;
459467 const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{}));
460 local_cache_directory.handle.writeFile(.{
468 local_cache_directory.handle.writeFile(io, .{
461469 .sub_path = tmp_sub_path,
462470 .data = buffer.items,
463471 .flags = .{ .exclusive = true },
......@@ -476,14 +484,14 @@ pub fn main() !void {
476484 validateSystemLibraryOptions(builder);
477485
478486 if (help_menu) {
479 var w = initStdoutWriter();
487 var w = initStdoutWriter(io);
480488 printUsage(builder, w) catch return stdout_writer_allocation.err.?;
481489 w.flush() catch return stdout_writer_allocation.err.?;
482490 return;
483491 }
484492
485493 if (steps_menu) {
486 var w = initStdoutWriter();
494 var w = initStdoutWriter(io);
487495 printSteps(builder, w) catch return stdout_writer_allocation.err.?;
488496 w.flush() catch return stdout_writer_allocation.err.?;
489497 return;
......@@ -507,8 +515,6 @@ pub fn main() !void {
507515 .error_style = error_style,
508516 .multiline_errors = multiline_errors,
509517 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,
510
511 .ttyconf = ttyconf,
512518 };
513519 defer {
514520 run.memory_blocked_steps.deinit(gpa);
......@@ -522,10 +528,10 @@ pub fn main() !void {
522528
523529 prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) {
524530 error.DependencyLoopDetected => {
525 // Perhaps in the future there could be an Advanced Options flag such as
526 // --debug-build-runner-leaks which would make this code return instead of
527 // calling exit.
528 std.debug.lockStdErr();
531 // Perhaps in the future there could be an Advanced Options flag
532 // such as --debug-build-runner-leaks which would make this code
533 // return instead of calling exit.
534 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
529535 process.exit(1);
530536 },
531537 else => |e| return e,
......@@ -543,7 +549,6 @@ pub fn main() !void {
543549 if (builtin.single_threaded) unreachable; // `fatal` above
544550 break :ws .init(.{
545551 .gpa = gpa,
546 .ttyconf = ttyconf,
547552 .graph = &graph,
548553 .all_steps = run.step_stack.keys(),
549554 .root_prog_node = main_progress_node,
......@@ -558,9 +563,9 @@ pub fn main() !void {
558563 }
559564
560565 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {
561 const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);
562 defer std.debug.unlockStderrWriter();
563 try bw.writeAll("\x1B[2J\x1B[3J\x1B[H");
566 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
567 defer io.unlockStderr();
568 try stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H");
564569 }) {
565570 if (run.web_server) |*ws| ws.startBuild();
566571
......@@ -661,9 +666,6 @@ const Run = struct {
661666 memory_blocked_steps: std.ArrayList(*Step),
662667 /// Allocated into `gpa`.
663668 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),
664 /// Similar to the `tty.Config` returned by `std.debug.lockStderrWriter`,
665 /// but also respects the '--color' flag.
666 ttyconf: tty.Config,
667669
668670 claimed_rss: usize,
669671 error_style: ErrorStyle,
......@@ -737,7 +739,8 @@ fn runStepNames(
737739 fuzz: ?std.Build.Fuzz.Mode,
738740) !void {
739741 const gpa = run.gpa;
740 const io = b.graph.io;
742 const graph = b.graph;
743 const io = graph.io;
741744 const step_stack = &run.step_stack;
742745
743746 {
......@@ -822,7 +825,7 @@ fn runStepNames(
822825 }
823826 if (@bitSizeOf(usize) != 64) {
824827 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,
825 // being compatible with `std.fs.getEndPos() u64`'s return value. This is not the case
828 // being compatible with file system's u64 return value. This is not the case
826829 // on 32-bit platforms.
827830 // Affects or affected by issues #5185, #22523, and #22464.
828831 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
......@@ -837,7 +840,6 @@ fn runStepNames(
837840 var f = std.Build.Fuzz.init(
838841 gpa,
839842 io,
840 run.ttyconf,
841843 step_stack.keys(),
842844 parent_prog_node,
843845 mode,
......@@ -864,18 +866,19 @@ fn runStepNames(
864866 .none => break :summary,
865867 }
866868
867 const w, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);
868 defer std.debug.unlockStderrWriter();
869 const ttyconf = run.ttyconf;
869 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
870 defer io.unlockStderr();
871 const t = stderr.terminal();
872 const w = &stderr.file_writer.interface;
870873
871874 const total_count = success_count + failure_count + pending_count + skipped_count;
872 ttyconf.setColor(w, .cyan) catch {};
873 ttyconf.setColor(w, .bold) catch {};
875 t.setColor(.cyan) catch {};
876 t.setColor(.bold) catch {};
874877 w.writeAll("Build Summary: ") catch {};
875 ttyconf.setColor(w, .reset) catch {};
878 t.setColor(.reset) catch {};
876879 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
877880 {
878 ttyconf.setColor(w, .dim) catch {};
881 t.setColor(.dim) catch {};
879882 var first = true;
880883 if (skipped_count > 0) {
881884 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};
......@@ -886,12 +889,12 @@ fn runStepNames(
886889 first = false;
887890 }
888891 if (!first) w.writeByte(')') catch {};
889 ttyconf.setColor(w, .reset) catch {};
892 t.setColor(.reset) catch {};
890893 }
891894
892895 if (test_count > 0) {
893896 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
894 ttyconf.setColor(w, .dim) catch {};
897 t.setColor(.dim) catch {};
895898 var first = true;
896899 if (test_skip_count > 0) {
897900 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};
......@@ -910,7 +913,7 @@ fn runStepNames(
910913 first = false;
911914 }
912915 if (!first) w.writeByte(')') catch {};
913 ttyconf.setColor(w, .reset) catch {};
916 t.setColor(.reset) catch {};
914917 }
915918
916919 w.writeAll("\n") catch {};
......@@ -924,7 +927,7 @@ fn runStepNames(
924927 var print_node: PrintNode = .{ .parent = null };
925928 if (step_names.len == 0) {
926929 print_node.last = true;
927 printTreeStep(b, b.default_step, run, w, ttyconf, &print_node, &step_stack_copy) catch {};
930 printTreeStep(b, b.default_step, run, t, &print_node, &step_stack_copy) catch {};
928931 } else {
929932 const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {
930933 var i: usize = step_names.len;
......@@ -943,7 +946,7 @@ fn runStepNames(
943946 for (step_names, 0..) |step_name, i| {
944947 const tls = b.top_level_steps.get(step_name).?;
945948 print_node.last = i + 1 == last_index;
946 printTreeStep(b, &tls.step, run, w, ttyconf, &print_node, &step_stack_copy) catch {};
949 printTreeStep(b, &tls.step, run, t, &print_node, &step_stack_copy) catch {};
947950 }
948951 }
949952 w.writeByte('\n') catch {};
......@@ -960,7 +963,7 @@ fn runStepNames(
960963 if (run.error_style.verboseContext()) break :code 1; // failure; print build command
961964 break :code 2; // failure; do not print build command
962965 };
963 std.debug.lockStdErr();
966 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
964967 process.exit(code);
965968}
966969
......@@ -969,33 +972,30 @@ const PrintNode = struct {
969972 last: bool = false,
970973};
971974
972fn printPrefix(node: *PrintNode, stderr: *Writer, ttyconf: tty.Config) !void {
975fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void {
973976 const parent = node.parent orelse return;
977 const writer = stderr.writer;
974978 if (parent.parent == null) return;
975 try printPrefix(parent, stderr, ttyconf);
979 try printPrefix(parent, stderr);
976980 if (parent.last) {
977 try stderr.writeAll(" ");
981 try writer.writeAll(" ");
978982 } else {
979 try stderr.writeAll(switch (ttyconf) {
980 .no_color, .windows_api => "| ",
983 try writer.writeAll(switch (stderr.mode) {
981984 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
985 else => "| ",
982986 });
983987 }
984988}
985989
986fn printChildNodePrefix(stderr: *Writer, ttyconf: tty.Config) !void {
987 try stderr.writeAll(switch (ttyconf) {
988 .no_color, .windows_api => "+- ",
990fn printChildNodePrefix(stderr: Io.Terminal) !void {
991 try stderr.writer.writeAll(switch (stderr.mode) {
989992 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
993 else => "+- ",
990994 });
991995}
992996
993fn printStepStatus(
994 s: *Step,
995 stderr: *Writer,
996 ttyconf: tty.Config,
997 run: *const Run,
998) !void {
997fn printStepStatus(s: *Step, stderr: Io.Terminal, run: *const Run) !void {
998 const writer = stderr.writer;
999999 switch (s.state) {
10001000 .precheck_unstarted => unreachable,
10011001 .precheck_started => unreachable,
......@@ -1003,139 +1003,135 @@ fn printStepStatus(
10031003 .running => unreachable,
10041004
10051005 .dependency_failure => {
1006 try ttyconf.setColor(stderr, .dim);
1007 try stderr.writeAll(" transitive failure\n");
1008 try ttyconf.setColor(stderr, .reset);
1006 try stderr.setColor(.dim);
1007 try writer.writeAll(" transitive failure\n");
1008 try stderr.setColor(.reset);
10091009 },
10101010
10111011 .success => {
1012 try ttyconf.setColor(stderr, .green);
1012 try stderr.setColor(.green);
10131013 if (s.result_cached) {
1014 try stderr.writeAll(" cached");
1014 try writer.writeAll(" cached");
10151015 } else if (s.test_results.test_count > 0) {
10161016 const pass_count = s.test_results.passCount();
10171017 assert(s.test_results.test_count == pass_count + s.test_results.skip_count);
1018 try stderr.print(" {d} pass", .{pass_count});
1018 try writer.print(" {d} pass", .{pass_count});
10191019 if (s.test_results.skip_count > 0) {
1020 try ttyconf.setColor(stderr, .reset);
1021 try stderr.writeAll(", ");
1022 try ttyconf.setColor(stderr, .yellow);
1023 try stderr.print("{d} skip", .{s.test_results.skip_count});
1020 try stderr.setColor(.reset);
1021 try writer.writeAll(", ");
1022 try stderr.setColor(.yellow);
1023 try writer.print("{d} skip", .{s.test_results.skip_count});
10241024 }
1025 try ttyconf.setColor(stderr, .reset);
1026 try stderr.print(" ({d} total)", .{s.test_results.test_count});
1025 try stderr.setColor(.reset);
1026 try writer.print(" ({d} total)", .{s.test_results.test_count});
10271027 } else {
1028 try stderr.writeAll(" success");
1028 try writer.writeAll(" success");
10291029 }
1030 try ttyconf.setColor(stderr, .reset);
1030 try stderr.setColor(.reset);
10311031 if (s.result_duration_ns) |ns| {
1032 try ttyconf.setColor(stderr, .dim);
1032 try stderr.setColor(.dim);
10331033 if (ns >= std.time.ns_per_min) {
1034 try stderr.print(" {d}m", .{ns / std.time.ns_per_min});
1034 try writer.print(" {d}m", .{ns / std.time.ns_per_min});
10351035 } else if (ns >= std.time.ns_per_s) {
1036 try stderr.print(" {d}s", .{ns / std.time.ns_per_s});
1036 try writer.print(" {d}s", .{ns / std.time.ns_per_s});
10371037 } else if (ns >= std.time.ns_per_ms) {
1038 try stderr.print(" {d}ms", .{ns / std.time.ns_per_ms});
1038 try writer.print(" {d}ms", .{ns / std.time.ns_per_ms});
10391039 } else if (ns >= std.time.ns_per_us) {
1040 try stderr.print(" {d}us", .{ns / std.time.ns_per_us});
1040 try writer.print(" {d}us", .{ns / std.time.ns_per_us});
10411041 } else {
1042 try stderr.print(" {d}ns", .{ns});
1042 try writer.print(" {d}ns", .{ns});
10431043 }
1044 try ttyconf.setColor(stderr, .reset);
1044 try stderr.setColor(.reset);
10451045 }
10461046 if (s.result_peak_rss != 0) {
10471047 const rss = s.result_peak_rss;
1048 try ttyconf.setColor(stderr, .dim);
1048 try stderr.setColor(.dim);
10491049 if (rss >= 1000_000_000) {
1050 try stderr.print(" MaxRSS:{d}G", .{rss / 1000_000_000});
1050 try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000});
10511051 } else if (rss >= 1000_000) {
1052 try stderr.print(" MaxRSS:{d}M", .{rss / 1000_000});
1052 try writer.print(" MaxRSS:{d}M", .{rss / 1000_000});
10531053 } else if (rss >= 1000) {
1054 try stderr.print(" MaxRSS:{d}K", .{rss / 1000});
1054 try writer.print(" MaxRSS:{d}K", .{rss / 1000});
10551055 } else {
1056 try stderr.print(" MaxRSS:{d}B", .{rss});
1056 try writer.print(" MaxRSS:{d}B", .{rss});
10571057 }
1058 try ttyconf.setColor(stderr, .reset);
1058 try stderr.setColor(.reset);
10591059 }
1060 try stderr.writeAll("\n");
1060 try writer.writeAll("\n");
10611061 },
10621062 .skipped, .skipped_oom => |skip| {
1063 try ttyconf.setColor(stderr, .yellow);
1064 try stderr.writeAll(" skipped");
1063 try stderr.setColor(.yellow);
1064 try writer.writeAll(" skipped");
10651065 if (skip == .skipped_oom) {
1066 try stderr.writeAll(" (not enough memory)");
1067 try ttyconf.setColor(stderr, .dim);
1068 try stderr.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
1069 try ttyconf.setColor(stderr, .yellow);
1066 try writer.writeAll(" (not enough memory)");
1067 try stderr.setColor(.dim);
1068 try writer.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
1069 try stderr.setColor(.yellow);
10701070 }
1071 try stderr.writeAll("\n");
1072 try ttyconf.setColor(stderr, .reset);
1071 try writer.writeAll("\n");
1072 try stderr.setColor(.reset);
10731073 },
10741074 .failure => {
1075 try printStepFailure(s, stderr, ttyconf, false);
1076 try ttyconf.setColor(stderr, .reset);
1075 try printStepFailure(s, stderr, false);
1076 try stderr.setColor(.reset);
10771077 },
10781078 }
10791079}
10801080
1081fn printStepFailure(
1082 s: *Step,
1083 stderr: *Writer,
1084 ttyconf: tty.Config,
1085 dim: bool,
1086) !void {
1081fn printStepFailure(s: *Step, stderr: Io.Terminal, dim: bool) !void {
1082 const w = stderr.writer;
10871083 if (s.result_error_bundle.errorMessageCount() > 0) {
1088 try ttyconf.setColor(stderr, .red);
1089 try stderr.print(" {d} errors\n", .{
1084 try stderr.setColor(.red);
1085 try w.print(" {d} errors\n", .{
10901086 s.result_error_bundle.errorMessageCount(),
10911087 });
10921088 } else if (!s.test_results.isSuccess()) {
10931089 // These first values include all of the test "statuses". Every test is either passsed,
10941090 // skipped, failed, crashed, or timed out.
1095 try ttyconf.setColor(stderr, .green);
1096 try stderr.print(" {d} pass", .{s.test_results.passCount()});
1097 try ttyconf.setColor(stderr, .reset);
1098 if (dim) try ttyconf.setColor(stderr, .dim);
1091 try stderr.setColor(.green);
1092 try w.print(" {d} pass", .{s.test_results.passCount()});
1093 try stderr.setColor(.reset);
1094 if (dim) try stderr.setColor(.dim);
10991095 if (s.test_results.skip_count > 0) {
1100 try stderr.writeAll(", ");
1101 try ttyconf.setColor(stderr, .yellow);
1102 try stderr.print("{d} skip", .{s.test_results.skip_count});
1103 try ttyconf.setColor(stderr, .reset);
1104 if (dim) try ttyconf.setColor(stderr, .dim);
1096 try w.writeAll(", ");
1097 try stderr.setColor(.yellow);
1098 try w.print("{d} skip", .{s.test_results.skip_count});
1099 try stderr.setColor(.reset);
1100 if (dim) try stderr.setColor(.dim);
11051101 }
11061102 if (s.test_results.fail_count > 0) {
1107 try stderr.writeAll(", ");
1108 try ttyconf.setColor(stderr, .red);
1109 try stderr.print("{d} fail", .{s.test_results.fail_count});
1110 try ttyconf.setColor(stderr, .reset);
1111 if (dim) try ttyconf.setColor(stderr, .dim);
1103 try w.writeAll(", ");
1104 try stderr.setColor(.red);
1105 try w.print("{d} fail", .{s.test_results.fail_count});
1106 try stderr.setColor(.reset);
1107 if (dim) try stderr.setColor(.dim);
11121108 }
11131109 if (s.test_results.crash_count > 0) {
1114 try stderr.writeAll(", ");
1115 try ttyconf.setColor(stderr, .red);
1116 try stderr.print("{d} crash", .{s.test_results.crash_count});
1117 try ttyconf.setColor(stderr, .reset);
1118 if (dim) try ttyconf.setColor(stderr, .dim);
1110 try w.writeAll(", ");
1111 try stderr.setColor(.red);
1112 try w.print("{d} crash", .{s.test_results.crash_count});
1113 try stderr.setColor(.reset);
1114 if (dim) try stderr.setColor(.dim);
11191115 }
11201116 if (s.test_results.timeout_count > 0) {
1121 try stderr.writeAll(", ");
1122 try ttyconf.setColor(stderr, .red);
1123 try stderr.print("{d} timeout", .{s.test_results.timeout_count});
1124 try ttyconf.setColor(stderr, .reset);
1125 if (dim) try ttyconf.setColor(stderr, .dim);
1117 try w.writeAll(", ");
1118 try stderr.setColor(.red);
1119 try w.print("{d} timeout", .{s.test_results.timeout_count});
1120 try stderr.setColor(.reset);
1121 if (dim) try stderr.setColor(.dim);
11261122 }
1127 try stderr.print(" ({d} total)", .{s.test_results.test_count});
1123 try w.print(" ({d} total)", .{s.test_results.test_count});
11281124
11291125 // Memory leaks are intentionally written after the total, because is isn't a test *status*,
11301126 // but just a flag that any tests -- even passed ones -- can have. We also use a different
11311127 // separator, so it looks like:
11321128 // 2 pass, 1 skip, 2 fail (5 total); 2 leaks
11331129 if (s.test_results.leak_count > 0) {
1134 try stderr.writeAll("; ");
1135 try ttyconf.setColor(stderr, .red);
1136 try stderr.print("{d} leaks", .{s.test_results.leak_count});
1137 try ttyconf.setColor(stderr, .reset);
1138 if (dim) try ttyconf.setColor(stderr, .dim);
1130 try w.writeAll("; ");
1131 try stderr.setColor(.red);
1132 try w.print("{d} leaks", .{s.test_results.leak_count});
1133 try stderr.setColor(.reset);
1134 if (dim) try stderr.setColor(.dim);
11391135 }
11401136
11411137 // It's usually not helpful to know how many error logs there were because they tend to
......@@ -1148,21 +1144,21 @@ fn printStepFailure(
11481144 break :show alt_results.isSuccess();
11491145 };
11501146 if (show_err_logs) {
1151 try stderr.writeAll("; ");
1152 try ttyconf.setColor(stderr, .red);
1153 try stderr.print("{d} error logs", .{s.test_results.log_err_count});
1154 try ttyconf.setColor(stderr, .reset);
1155 if (dim) try ttyconf.setColor(stderr, .dim);
1147 try w.writeAll("; ");
1148 try stderr.setColor(.red);
1149 try w.print("{d} error logs", .{s.test_results.log_err_count});
1150 try stderr.setColor(.reset);
1151 if (dim) try stderr.setColor(.dim);
11561152 }
11571153
1158 try stderr.writeAll("\n");
1154 try w.writeAll("\n");
11591155 } else if (s.result_error_msgs.items.len > 0) {
1160 try ttyconf.setColor(stderr, .red);
1161 try stderr.writeAll(" failure\n");
1156 try stderr.setColor(.red);
1157 try w.writeAll(" failure\n");
11621158 } else {
11631159 assert(s.result_stderr.len > 0);
1164 try ttyconf.setColor(stderr, .red);
1165 try stderr.writeAll(" stderr\n");
1160 try stderr.setColor(.red);
1161 try w.writeAll(" w\n");
11661162 }
11671163}
11681164
......@@ -1170,11 +1166,11 @@ fn printTreeStep(
11701166 b: *std.Build,
11711167 s: *Step,
11721168 run: *const Run,
1173 stderr: *Writer,
1174 ttyconf: tty.Config,
1169 stderr: Io.Terminal,
11751170 parent_node: *PrintNode,
11761171 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
11771172) !void {
1173 const writer = stderr.writer;
11781174 const first = step_stack.swapRemove(s);
11791175 const summary = run.summary;
11801176 const skip = switch (summary) {
......@@ -1184,26 +1180,26 @@ fn printTreeStep(
11841180 .failures => s.state == .success,
11851181 };
11861182 if (skip) return;
1187 try printPrefix(parent_node, stderr, ttyconf);
1183 try printPrefix(parent_node, stderr);
11881184
11891185 if (parent_node.parent != null) {
11901186 if (parent_node.last) {
1191 try printChildNodePrefix(stderr, ttyconf);
1187 try printChildNodePrefix(stderr);
11921188 } else {
1193 try stderr.writeAll(switch (ttyconf) {
1194 .no_color, .windows_api => "+- ",
1189 try writer.writeAll(switch (stderr.mode) {
11951190 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
1191 else => "+- ",
11961192 });
11971193 }
11981194 }
11991195
1200 if (!first) try ttyconf.setColor(stderr, .dim);
1196 if (!first) try stderr.setColor(.dim);
12011197
12021198 // dep_prefix omitted here because it is redundant with the tree.
1203 try stderr.writeAll(s.name);
1199 try writer.writeAll(s.name);
12041200
12051201 if (first) {
1206 try printStepStatus(s, stderr, ttyconf, run);
1202 try printStepStatus(s, stderr, run);
12071203
12081204 const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: {
12091205 var i: usize = s.dependencies.items.len;
......@@ -1225,17 +1221,17 @@ fn printTreeStep(
12251221 .parent = parent_node,
12261222 .last = i == last_index,
12271223 };
1228 try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack);
1224 try printTreeStep(b, dep, run, stderr, &print_node, step_stack);
12291225 }
12301226 } else {
12311227 if (s.dependencies.items.len == 0) {
1232 try stderr.writeAll(" (reused)\n");
1228 try writer.writeAll(" (reused)\n");
12331229 } else {
1234 try stderr.print(" (+{d} more reused dependencies)\n", .{
1230 try writer.print(" (+{d} more reused dependencies)\n", .{
12351231 s.dependencies.items.len,
12361232 });
12371233 }
1238 try ttyconf.setColor(stderr, .reset);
1234 try stderr.setColor(.reset);
12391235 }
12401236}
12411237
......@@ -1306,7 +1302,8 @@ fn workerMakeOneStep(
13061302 prog_node: std.Progress.Node,
13071303 run: *Run,
13081304) void {
1309 const io = b.graph.io;
1305 const graph = b.graph;
1306 const io = graph.io;
13101307 const gpa = run.gpa;
13111308
13121309 // First, check the conditions for running this step. If they are not met,
......@@ -1366,7 +1363,6 @@ fn workerMakeOneStep(
13661363 .progress_node = sub_prog_node,
13671364 .watch = run.watch,
13681365 .web_server = if (run.web_server) |*ws| ws else null,
1369 .ttyconf = run.ttyconf,
13701366 .unit_test_timeout_ns = run.unit_test_timeout_ns,
13711367 .gpa = gpa,
13721368 });
......@@ -1376,10 +1372,11 @@ fn workerMakeOneStep(
13761372 const show_error_msgs = s.result_error_msgs.items.len > 0;
13771373 const show_stderr = s.result_stderr.len > 0;
13781374 if (show_error_msgs or show_compile_errors or show_stderr) {
1379 const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);
1380 defer std.debug.unlockStderrWriter();
1381 const ttyconf = run.ttyconf;
1382 printErrorMessages(gpa, s, .{}, bw, ttyconf, run.error_style, run.multiline_errors) catch {};
1375 const stderr = io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode) catch |err| switch (err) {
1376 error.Canceled => return,
1377 };
1378 defer io.unlockStderr();
1379 printErrorMessages(gpa, s, .{}, stderr.terminal(), run.error_style, run.multiline_errors) catch {};
13831380 }
13841381
13851382 handle_result: {
......@@ -1446,11 +1443,11 @@ pub fn printErrorMessages(
14461443 gpa: Allocator,
14471444 failing_step: *Step,
14481445 options: std.zig.ErrorBundle.RenderOptions,
1449 stderr: *Writer,
1450 ttyconf: tty.Config,
1446 stderr: Io.Terminal,
14511447 error_style: ErrorStyle,
14521448 multiline_errors: MultilineErrors,
14531449) !void {
1450 const writer = stderr.writer;
14541451 if (error_style.verboseContext()) {
14551452 // Provide context for where these error messages are coming from by
14561453 // printing the corresponding Step subtree.
......@@ -1462,70 +1459,70 @@ pub fn printErrorMessages(
14621459 }
14631460
14641461 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1465 try ttyconf.setColor(stderr, .dim);
1462 try stderr.setColor(.dim);
14661463 var indent: usize = 0;
14671464 while (step_stack.pop()) |s| : (indent += 1) {
14681465 if (indent > 0) {
1469 try stderr.splatByteAll(' ', (indent - 1) * 3);
1470 try printChildNodePrefix(stderr, ttyconf);
1466 try writer.splatByteAll(' ', (indent - 1) * 3);
1467 try printChildNodePrefix(stderr);
14711468 }
14721469
1473 try stderr.writeAll(s.name);
1470 try writer.writeAll(s.name);
14741471
14751472 if (s == failing_step) {
1476 try printStepFailure(s, stderr, ttyconf, true);
1473 try printStepFailure(s, stderr, true);
14771474 } else {
1478 try stderr.writeAll("\n");
1475 try writer.writeAll("\n");
14791476 }
14801477 }
1481 try ttyconf.setColor(stderr, .reset);
1478 try stderr.setColor(.reset);
14821479 } else {
14831480 // Just print the failing step itself.
1484 try ttyconf.setColor(stderr, .dim);
1485 try stderr.writeAll(failing_step.name);
1486 try printStepFailure(failing_step, stderr, ttyconf, true);
1487 try ttyconf.setColor(stderr, .reset);
1481 try stderr.setColor(.dim);
1482 try writer.writeAll(failing_step.name);
1483 try printStepFailure(failing_step, stderr, true);
1484 try stderr.setColor(.reset);
14881485 }
14891486
14901487 if (failing_step.result_stderr.len > 0) {
1491 try stderr.writeAll(failing_step.result_stderr);
1488 try writer.writeAll(failing_step.result_stderr);
14921489 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1493 try stderr.writeAll("\n");
1490 try writer.writeAll("\n");
14941491 }
14951492 }
14961493
1497 try failing_step.result_error_bundle.renderToWriter(options, stderr, ttyconf);
1494 try failing_step.result_error_bundle.renderToTerminal(options, stderr);
14981495
14991496 for (failing_step.result_error_msgs.items) |msg| {
1500 try ttyconf.setColor(stderr, .red);
1501 try stderr.writeAll("error:");
1502 try ttyconf.setColor(stderr, .reset);
1497 try stderr.setColor(.red);
1498 try writer.writeAll("error:");
1499 try stderr.setColor(.reset);
15031500 if (std.mem.indexOfScalar(u8, msg, '\n') == null) {
1504 try stderr.print(" {s}\n", .{msg});
1501 try writer.print(" {s}\n", .{msg});
15051502 } else switch (multiline_errors) {
15061503 .indent => {
15071504 var it = std.mem.splitScalar(u8, msg, '\n');
1508 try stderr.print(" {s}\n", .{it.first()});
1505 try writer.print(" {s}\n", .{it.first()});
15091506 while (it.next()) |line| {
1510 try stderr.print(" {s}\n", .{line});
1507 try writer.print(" {s}\n", .{line});
15111508 }
15121509 },
1513 .newline => try stderr.print("\n{s}\n", .{msg}),
1514 .none => try stderr.print(" {s}\n", .{msg}),
1510 .newline => try writer.print("\n{s}\n", .{msg}),
1511 .none => try writer.print(" {s}\n", .{msg}),
15151512 }
15161513 }
15171514
15181515 if (error_style.verboseContext()) {
15191516 if (failing_step.result_failed_command) |cmd_str| {
1520 try ttyconf.setColor(stderr, .red);
1521 try stderr.writeAll("failed command: ");
1522 try ttyconf.setColor(stderr, .reset);
1523 try stderr.writeAll(cmd_str);
1524 try stderr.writeByte('\n');
1517 try stderr.setColor(.red);
1518 try writer.writeAll("failed command: ");
1519 try stderr.setColor(.reset);
1520 try writer.writeAll(cmd_str);
1521 try writer.writeByte('\n');
15251522 }
15261523 }
15271524
1528 try stderr.writeByte('\n');
1525 try writer.writeByte('\n');
15291526}
15301527
15311528fn printSteps(builder: *std.Build, w: *Writer) !void {
......@@ -1843,9 +1840,9 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {
18431840}
18441841
18451842var stdio_buffer_allocation: [256]u8 = undefined;
1846var stdout_writer_allocation: std.fs.File.Writer = undefined;
1843var stdout_writer_allocation: Io.File.Writer = undefined;
18471844
1848fn initStdoutWriter() *Writer {
1849 stdout_writer_allocation = std.fs.File.stdout().writerStreaming(&stdio_buffer_allocation);
1845fn initStdoutWriter(io: Io) *Writer {
1846 stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation);
18501847 return &stdout_writer_allocation.interface;
18511848}
lib/compiler/libc.zig+10-10
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const mem = std.mem;
34const LibCInstallation = std.zig.LibCInstallation;
45
......@@ -29,7 +30,7 @@ pub fn main() !void {
2930 const arena = arena_instance.allocator();
3031 const gpa = arena;
3132
32 var threaded: std.Io.Threaded = .init(gpa);
33 var threaded: std.Io.Threaded = .init(gpa, .{});
3334 defer threaded.deinit();
3435 const io = threaded.io();
3536
......@@ -39,7 +40,7 @@ pub fn main() !void {
3940 var input_file: ?[]const u8 = null;
4041 var target_arch_os_abi: []const u8 = "native";
4142 var print_includes: bool = false;
42 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
43 var stdout_writer = Io.File.stdout().writer(&stdout_buffer);
4344 const stdout = &stdout_writer.interface;
4445 {
4546 var i: usize = 2;
......@@ -49,7 +50,7 @@ pub fn main() !void {
4950 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
5051 try stdout.writeAll(usage_libc);
5152 try stdout.flush();
52 return std.process.cleanExit();
53 return std.process.cleanExit(io);
5354 } else if (mem.eql(u8, arg, "-target")) {
5455 if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});
5556 i += 1;
......@@ -77,7 +78,7 @@ pub fn main() !void {
7778 if (input_file) |libc_file| {
7879 const libc = try arena.create(LibCInstallation);
7980 libc.* = LibCInstallation.parse(arena, libc_file, &target) catch |err| {
80 fatal("unable to parse libc file at path {s}: {s}", .{ libc_file, @errorName(err) });
81 fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err });
8182 };
8283 break :libc libc;
8384 } else {
......@@ -96,7 +97,7 @@ pub fn main() !void {
9697 libc_installation,
9798 ) catch |err| {
9899 const zig_target = try target.zigTriple(arena);
99 fatal("unable to detect libc for target {s}: {s}", .{ zig_target, @errorName(err) });
100 fatal("unable to detect libc for target {s}: {t}", .{ zig_target, err });
100101 };
101102
102103 if (libc_dirs.libc_include_dir_list.len == 0) {
......@@ -109,24 +110,23 @@ pub fn main() !void {
109110 try stdout.writeByte('\n');
110111 }
111112 try stdout.flush();
112 return std.process.cleanExit();
113 return std.process.cleanExit(io);
113114 }
114115
115116 if (input_file) |libc_file| {
116117 var libc = LibCInstallation.parse(gpa, libc_file, &target) catch |err| {
117 fatal("unable to parse libc file at path {s}: {s}", .{ libc_file, @errorName(err) });
118 fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err });
118119 };
119120 defer libc.deinit(gpa);
120121 } else {
121122 if (!target_query.canDetectLibC()) {
122123 fatal("unable to detect libc for non-native target", .{});
123124 }
124 var libc = LibCInstallation.findNative(.{
125 .allocator = gpa,
125 var libc = LibCInstallation.findNative(gpa, io, .{
126126 .verbose = true,
127127 .target = &target,
128128 }) catch |err| {
129 fatal("unable to detect native libc: {s}", .{@errorName(err)});
129 fatal("unable to detect native libc: {t}", .{err});
130130 };
131131 defer libc.deinit(gpa);
132132
lib/compiler/objcopy.zig+24-22
......@@ -1,12 +1,13 @@
11const builtin = @import("builtin");
2
23const std = @import("std");
4const Io = std.Io;
35const mem = std.mem;
46const fs = std.fs;
57const elf = std.elf;
68const Allocator = std.mem.Allocator;
7const File = std.fs.File;
9const File = std.Io.File;
810const assert = std.debug.assert;
9
1011const fatal = std.process.fatal;
1112const Server = std.zig.Server;
1213
......@@ -24,11 +25,15 @@ pub fn main() !void {
2425 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
2526 const gpa = general_purpose_allocator.allocator();
2627
28 var threaded: std.Io.Threaded = .init(gpa, .{});
29 defer threaded.deinit();
30 const io = threaded.io();
31
2732 const args = try std.process.argsAlloc(arena);
28 return cmdObjCopy(gpa, arena, args[1..]);
33 return cmdObjCopy(arena, io, args[1..]);
2934}
3035
31fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
36fn cmdObjCopy(arena: Allocator, io: Io, args: []const []const u8) !void {
3237 var i: usize = 0;
3338 var opt_out_fmt: ?std.Target.ObjectFormat = null;
3439 var opt_input: ?[]const u8 = null;
......@@ -56,7 +61,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
5661 fatal("unexpected positional argument: '{s}'", .{arg});
5762 }
5863 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
59 return std.fs.File.stdout().writeAll(usage);
64 return Io.File.stdout().writeStreamingAll(io, usage);
6065 } else if (mem.eql(u8, arg, "-O") or mem.eql(u8, arg, "--output-target")) {
6166 i += 1;
6267 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
......@@ -147,16 +152,12 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
147152 const input = opt_input orelse fatal("expected input parameter", .{});
148153 const output = opt_output orelse fatal("expected output parameter", .{});
149154
150 var threaded: std.Io.Threaded = .init(gpa);
151 defer threaded.deinit();
152 const io = threaded.io();
153
154 const input_file = fs.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err });
155 defer input_file.close();
155 const input_file = Io.Dir.cwd().openFile(io, input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err });
156 defer input_file.close(io);
156157
157 const stat = input_file.stat() catch |err| fatal("failed to stat {s}: {t}", .{ input, err });
158 const stat = input_file.stat(io) catch |err| fatal("failed to stat {s}: {t}", .{ input, err });
158159
159 var in: File.Reader = .initSize(input_file.adaptToNewApi(), io, &input_buffer, stat.size);
160 var in: File.Reader = .initSize(input_file, io, &input_buffer, stat.size);
160161
161162 const elf_hdr = std.elf.Header.read(&in.interface) catch |err| switch (err) {
162163 error.ReadFailed => fatal("unable to read {s}: {t}", .{ input, in.err.? }),
......@@ -177,12 +178,12 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
177178 }
178179 };
179180
180 const mode = if (out_fmt != .elf or only_keep_debug) fs.File.default_mode else stat.mode;
181 const permissions: Io.File.Permissions = if (out_fmt != .elf or only_keep_debug) .default_file else stat.permissions;
181182
182 var output_file = try fs.cwd().createFile(output, .{ .mode = mode });
183 defer output_file.close();
183 var output_file = try Io.Dir.cwd().createFile(io, output, .{ .permissions = permissions });
184 defer output_file.close(io);
184185
185 var out = output_file.writer(&output_buffer);
186 var out = output_file.writer(io, &output_buffer);
186187
187188 switch (out_fmt) {
188189 .hex, .raw => {
......@@ -221,8 +222,8 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
221222 try out.end();
222223
223224 if (listen) {
224 var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer);
225 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
225 var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer);
226 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
226227 var server = try Server.init(.{
227228 .in = &stdin_reader.interface,
228229 .out = &stdout_writer.interface,
......@@ -234,7 +235,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
234235 const hdr = try server.receiveMessage();
235236 switch (hdr.tag) {
236237 .exit => {
237 return std.process.cleanExit();
238 return std.process.cleanExit(io);
238239 },
239240 .update => {
240241 if (seen_update) fatal("zig objcopy only supports 1 update for now", .{});
......@@ -249,7 +250,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
249250 }
250251 }
251252 }
252 return std.process.cleanExit();
253 return std.process.cleanExit(io);
253254}
254255
255256const usage =
......@@ -675,8 +676,9 @@ fn containsValidAddressRange(segments: []*BinaryElfSegment) bool {
675676}
676677
677678fn padFile(out: *File.Writer, opt_size: ?u64) !void {
679 const io = out.io;
678680 const size = opt_size orelse return;
679 try out.file.setEndPos(size);
681 try out.file.setLength(io, size);
680682}
681683
682684test "HexWriter.Record.Address has correct payload and checksum" {
lib/compiler/reduce.zig+24-22
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const mem = std.mem;
34const Allocator = std.mem.Allocator;
45const assert = std.debug.assert;
......@@ -54,6 +55,10 @@ pub fn main() !void {
5455 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
5556 const gpa = general_purpose_allocator.allocator();
5657
58 var threaded: std.Io.Threaded = .init(gpa, .{});
59 defer threaded.deinit();
60 const io = threaded.io();
61
5762 const args = try std.process.argsAlloc(arena);
5863
5964 var opt_checker_path: ?[]const u8 = null;
......@@ -68,9 +73,9 @@ pub fn main() !void {
6873 const arg = args[i];
6974 if (mem.startsWith(u8, arg, "-")) {
7075 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
71 const stdout = std.fs.File.stdout();
76 const stdout = Io.File.stdout();
7277 try stdout.writeAll(usage);
73 return std.process.cleanExit();
78 return std.process.cleanExit(io);
7479 } else if (mem.eql(u8, arg, "--")) {
7580 argv = args[i + 1 ..];
7681 break;
......@@ -87,9 +92,7 @@ pub fn main() !void {
8792 if (i >= args.len) fatal("expected 32-bit integer after {s}", .{arg});
8893 const next_arg = args[i];
8994 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
90 fatal("unable to parse seed '{s}' as 32-bit integer: {s}", .{
91 next_arg, @errorName(err),
92 });
95 fatal("unable to parse seed '{s}' as 32-bit integer: {t}", .{ next_arg, err });
9396 };
9497 } else {
9598 fatal("unrecognized parameter: '{s}'", .{arg});
......@@ -120,7 +123,7 @@ pub fn main() !void {
120123 var astgen_input: std.Io.Writer.Allocating = .init(gpa);
121124 defer astgen_input.deinit();
122125
123 var tree = try parse(gpa, root_source_file_path);
126 var tree = try parse(gpa, io, root_source_file_path);
124127 defer {
125128 gpa.free(tree.source);
126129 tree.deinit(gpa);
......@@ -185,7 +188,7 @@ pub fn main() !void {
185188 std.debug.print("{s} ", .{@tagName(t)});
186189 }
187190 std.debug.print("\n", .{});
188 try transformationsToFixups(gpa, arena, root_source_file_path, this_set, &fixups);
191 try transformationsToFixups(gpa, arena, io, root_source_file_path, this_set, &fixups);
189192
190193 rendered.clearRetainingCapacity();
191194 try tree.render(gpa, &rendered.writer, fixups);
......@@ -232,16 +235,16 @@ pub fn main() !void {
232235 }
233236 }
234237
235 try std.fs.cwd().writeFile(.{ .sub_path = root_source_file_path, .data = rendered.written() });
238 try Io.Dir.cwd().writeFile(io, .{ .sub_path = root_source_file_path, .data = rendered.written() });
236239 // std.debug.print("trying this code:\n{s}\n", .{rendered.items});
237240
238241 const interestingness = try runCheck(arena, interestingness_argv.items);
239 std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{
240 subset_size, @tagName(interestingness), start_index, transformations.items.len,
242 std.debug.print("{d} random transformations: {t}. {d}/{d}\n", .{
243 subset_size, interestingness, start_index, transformations.items.len,
241244 });
242245 switch (interestingness) {
243246 .interesting => {
244 const new_tree = try parse(gpa, root_source_file_path);
247 const new_tree = try parse(gpa, io, root_source_file_path);
245248 gpa.free(tree.source);
246249 tree.deinit(gpa);
247250 tree = new_tree;
......@@ -273,12 +276,12 @@ pub fn main() !void {
273276 fixups.clearRetainingCapacity();
274277 rendered.clearRetainingCapacity();
275278 try tree.render(gpa, &rendered.writer, fixups);
276 try std.fs.cwd().writeFile(.{ .sub_path = root_source_file_path, .data = rendered.written() });
279 try Io.Dir.cwd().writeFile(io, .{ .sub_path = root_source_file_path, .data = rendered.written() });
277280
278 return std.process.cleanExit();
281 return std.process.cleanExit(io);
279282 }
280283 std.debug.print("no more transformations found\n", .{});
281 return std.process.cleanExit();
284 return std.process.cleanExit(io);
282285}
283286
284287fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) void {
......@@ -302,11 +305,8 @@ fn termToInteresting(term: std.process.Child.Term) Interestingness {
302305 };
303306}
304307
305fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness {
306 const result = try std.process.Child.run(.{
307 .allocator = arena,
308 .argv = argv,
309 });
308fn runCheck(arena: Allocator, io: Io, argv: []const []const u8) !Interestingness {
309 const result = try std.process.Child.run(arena, io, .{ .argv = argv });
310310 if (result.stderr.len != 0)
311311 std.debug.print("{s}", .{result.stderr});
312312 return termToInteresting(result.term);
......@@ -315,6 +315,7 @@ fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness
315315fn transformationsToFixups(
316316 gpa: Allocator,
317317 arena: Allocator,
318 io: Io,
318319 root_source_file_path: []const u8,
319320 transforms: []const Walk.Transformation,
320321 fixups: *Ast.Render.Fixups,
......@@ -352,7 +353,7 @@ fn transformationsToFixups(
352353 inline_imported_file.imported_string,
353354 });
354355 defer gpa.free(full_imported_path);
355 var other_file_ast = try parse(gpa, full_imported_path);
356 var other_file_ast = try parse(gpa, io, full_imported_path);
356357 defer {
357358 gpa.free(other_file_ast.source);
358359 other_file_ast.deinit(gpa);
......@@ -396,8 +397,9 @@ fn transformationsToFixups(
396397 };
397398}
398399
399fn parse(gpa: Allocator, file_path: []const u8) !Ast {
400 const source_code = std.fs.cwd().readFileAllocOptions(
400fn parse(gpa: Allocator, io: Io, file_path: []const u8) !Ast {
401 const source_code = Io.Dir.cwd().readFileAllocOptions(
402 io,
401403 file_path,
402404 gpa,
403405 .limited(std.math.maxInt(u32)),
lib/compiler/resinator/cli.zig+43-39
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const code_pages = @import("code_pages.zig");
34const SupportedCodePage = code_pages.SupportedCodePage;
45const lang = @import("lang.zig");
......@@ -124,15 +125,15 @@ pub const Diagnostics = struct {
124125 try self.errors.append(self.allocator, error_details);
125126 }
126127
127 pub fn renderToStdErr(self: *Diagnostics, args: []const []const u8) void {
128 const stderr, const ttyconf = std.debug.lockStderrWriter(&.{});
129 defer std.debug.unlockStderrWriter();
130 self.renderToWriter(args, stderr, ttyconf) catch return;
128 pub fn renderToStderr(self: *Diagnostics, io: Io, args: []const []const u8) Io.Cancelable!void {
129 const stderr = try io.lockStderr(&.{}, null);
130 defer io.unlockStderr();
131 self.renderToWriter(args, stderr.terminal()) catch return;
131132 }
132133
133 pub fn renderToWriter(self: *Diagnostics, args: []const []const u8, writer: *std.Io.Writer, config: std.Io.tty.Config) !void {
134 pub fn renderToWriter(self: *Diagnostics, args: []const []const u8, t: Io.Terminal) !void {
134135 for (self.errors.items) |err_details| {
135 try renderErrorMessage(writer, config, err_details, args);
136 try renderErrorMessage(t, err_details, args);
136137 }
137138 }
138139
......@@ -169,7 +170,7 @@ pub const Options = struct {
169170 coff_options: cvtres.CoffOptions = .{},
170171
171172 pub const IoSource = union(enum) {
172 stdio: std.fs.File,
173 stdio: Io.File,
173174 filename: []const u8,
174175 };
175176 pub const AutoIncludes = enum { any, msvc, gnu, none };
......@@ -249,13 +250,13 @@ pub const Options = struct {
249250 /// worlds' situation where we'll be compatible with most use-cases
250251 /// of the .rc extension being omitted from the CLI args, but still
251252 /// work fine if the file itself does not have an extension.
252 pub fn maybeAppendRC(options: *Options, cwd: std.fs.Dir) !void {
253 pub fn maybeAppendRC(options: *Options, io: Io, cwd: Io.Dir) !void {
253254 switch (options.input_source) {
254255 .stdio => return,
255256 .filename => {},
256257 }
257258 if (options.input_format == .rc and std.fs.path.extension(options.input_source.filename).len == 0) {
258 cwd.access(options.input_source.filename, .{}) catch |err| switch (err) {
259 cwd.access(io, options.input_source.filename, .{}) catch |err| switch (err) {
259260 error.FileNotFound => {
260261 var filename_bytes = try options.allocator.alloc(u8, options.input_source.filename.len + 3);
261262 @memcpy(filename_bytes[0..options.input_source.filename.len], options.input_source.filename);
......@@ -418,7 +419,7 @@ pub const Arg = struct {
418419 };
419420 }
420421
421 pub fn looksLikeFilepath(self: Arg) bool {
422 pub fn looksLikeFilepath(self: Arg, io: Io) bool {
422423 const meets_min_requirements = self.prefix == .slash and isSupportedInputExtension(std.fs.path.extension(self.full));
423424 if (!meets_min_requirements) return false;
424425
......@@ -437,7 +438,7 @@ pub const Arg = struct {
437438 // It's still possible for a file path to look like a /fo option but not actually
438439 // be one, e.g. `/foo/bar.rc`. As a last ditch effort to reduce false negatives,
439440 // check if the file path exists and, if so, then we ignore the 'could be /fo option'-ness
440 std.fs.accessAbsolute(self.full, .{}) catch return false;
441 Io.Dir.accessAbsolute(io, self.full, .{}) catch return false;
441442 return true;
442443 }
443444
......@@ -489,7 +490,7 @@ pub const ParseError = error{ParseError} || Allocator.Error;
489490
490491/// Note: Does not run `Options.maybeAppendRC` automatically. If that behavior is desired,
491492/// it must be called separately.
492pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagnostics) ParseError!Options {
493pub fn parse(allocator: Allocator, io: Io, args: []const []const u8, diagnostics: *Diagnostics) ParseError!Options {
493494 var options = Options{ .allocator = allocator };
494495 errdefer options.deinit();
495496
......@@ -529,7 +530,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
529530 }
530531
531532 const args_remaining = args.len - arg_i;
532 if (args_remaining <= 2 and arg.looksLikeFilepath()) {
533 if (args_remaining <= 2 and arg.looksLikeFilepath(io)) {
533534 var err_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = true, .arg_index = arg_i };
534535 try err_details.msg.appendSlice(allocator, "this argument was inferred to be a filepath, so argument parsing was terminated");
535536 try diagnostics.append(err_details);
......@@ -1343,41 +1344,42 @@ test parsePercent {
13431344 try std.testing.expectError(error.InvalidFormat, parsePercent("~1"));
13441345}
13451346
1346pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err_details: Diagnostics.ErrorDetails, args: []const []const u8) !void {
1347 try config.setColor(writer, .dim);
1347pub fn renderErrorMessage(t: Io.Terminal, err_details: Diagnostics.ErrorDetails, args: []const []const u8) !void {
1348 const writer = t.writer;
1349 try t.setColor(.dim);
13481350 try writer.writeAll("<cli>");
1349 try config.setColor(writer, .reset);
1350 try config.setColor(writer, .bold);
1351 try t.setColor(.reset);
1352 try t.setColor(.bold);
13511353 try writer.writeAll(": ");
13521354 switch (err_details.type) {
13531355 .err => {
1354 try config.setColor(writer, .red);
1356 try t.setColor(.red);
13551357 try writer.writeAll("error: ");
13561358 },
13571359 .warning => {
1358 try config.setColor(writer, .yellow);
1360 try t.setColor(.yellow);
13591361 try writer.writeAll("warning: ");
13601362 },
13611363 .note => {
1362 try config.setColor(writer, .cyan);
1364 try t.setColor(.cyan);
13631365 try writer.writeAll("note: ");
13641366 },
13651367 }
1366 try config.setColor(writer, .reset);
1367 try config.setColor(writer, .bold);
1368 try t.setColor(.reset);
1369 try t.setColor(.bold);
13681370 try writer.writeAll(err_details.msg.items);
13691371 try writer.writeByte('\n');
1370 try config.setColor(writer, .reset);
1372 try t.setColor(.reset);
13711373
13721374 if (!err_details.print_args) {
13731375 try writer.writeByte('\n');
13741376 return;
13751377 }
13761378
1377 try config.setColor(writer, .dim);
1379 try t.setColor(.dim);
13781380 const prefix = " ... ";
13791381 try writer.writeAll(prefix);
1380 try config.setColor(writer, .reset);
1382 try t.setColor(.reset);
13811383
13821384 const arg_with_name = args[err_details.arg_index];
13831385 const prefix_slice = arg_with_name[0..err_details.arg_span.prefix_len];
......@@ -1388,15 +1390,15 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err
13881390
13891391 try writer.writeAll(prefix_slice);
13901392 if (before_name_slice.len > 0) {
1391 try config.setColor(writer, .dim);
1393 try t.setColor(.dim);
13921394 try writer.writeAll(before_name_slice);
1393 try config.setColor(writer, .reset);
1395 try t.setColor(.reset);
13941396 }
13951397 try writer.writeAll(name_slice);
13961398 if (after_name_slice.len > 0) {
1397 try config.setColor(writer, .dim);
1399 try t.setColor(.dim);
13981400 try writer.writeAll(after_name_slice);
1399 try config.setColor(writer, .reset);
1401 try t.setColor(.reset);
14001402 }
14011403
14021404 var next_arg_len: usize = 0;
......@@ -1414,13 +1416,13 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err
14141416 if (err_details.arg_span.value_offset >= arg_with_name.len) {
14151417 try writer.writeByte(' ');
14161418 }
1417 try config.setColor(writer, .dim);
1419 try t.setColor(.dim);
14181420 try writer.writeAll(" ...");
1419 try config.setColor(writer, .reset);
1421 try t.setColor(.reset);
14201422 }
14211423 try writer.writeByte('\n');
14221424
1423 try config.setColor(writer, .green);
1425 try t.setColor(.green);
14241426 try writer.splatByteAll(' ', prefix.len);
14251427 // Special case for when the option is *only* a prefix (e.g. invalid option: -)
14261428 if (err_details.arg_span.prefix_len == arg_with_name.len) {
......@@ -1446,7 +1448,7 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err
14461448 }
14471449 }
14481450 try writer.writeByte('\n');
1449 try config.setColor(writer, .reset);
1451 try t.setColor(.reset);
14501452}
14511453
14521454fn testParse(args: []const []const u8) !Options {
......@@ -1991,6 +1993,8 @@ test "parse: input and output formats" {
19911993}
19921994
19931995test "maybeAppendRC" {
1996 const io = std.testing.io;
1997
19941998 var tmp = std.testing.tmpDir(.{});
19951999 defer tmp.cleanup();
19962000
......@@ -2000,21 +2004,21 @@ test "maybeAppendRC" {
20002004
20012005 // Create the file so that it's found. In this scenario, .rc should not get
20022006 // appended.
2003 var file = try tmp.dir.createFile("foo", .{});
2004 file.close();
2005 try options.maybeAppendRC(tmp.dir);
2007 var file = try tmp.dir.createFile(io, "foo", .{});
2008 file.close(io);
2009 try options.maybeAppendRC(io, tmp.dir);
20062010 try std.testing.expectEqualStrings("foo", options.input_source.filename);
20072011
20082012 // Now delete the file and try again. But this time change the input format
20092013 // to non-rc.
2010 try tmp.dir.deleteFile("foo");
2014 try tmp.dir.deleteFile(io, "foo");
20112015 options.input_format = .res;
2012 try options.maybeAppendRC(tmp.dir);
2016 try options.maybeAppendRC(io, tmp.dir);
20132017 try std.testing.expectEqualStrings("foo", options.input_source.filename);
20142018
20152019 // Finally, reset the input format to rc. Since the verbatim name is no longer found
20162020 // and the input filename does not have an extension, .rc should get appended.
20172021 options.input_format = .rc;
2018 try options.maybeAppendRC(tmp.dir);
2022 try options.maybeAppendRC(io, tmp.dir);
20192023 try std.testing.expectEqualStrings("foo.rc", options.input_source.filename);
20202024}
lib/compiler/resinator/compile.zig+28-26
......@@ -34,7 +34,7 @@ const code_pages = @import("code_pages.zig");
3434const errors = @import("errors.zig");
3535
3636pub const CompileOptions = struct {
37 cwd: std.fs.Dir,
37 cwd: std.Io.Dir,
3838 diagnostics: *Diagnostics,
3939 source_mappings: ?*SourceMappings = null,
4040 /// List of paths (absolute or relative to `cwd`) for every file that the resources within the .rc file depend on.
......@@ -96,7 +96,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io
9696 var search_dirs: std.ArrayList(SearchDir) = .empty;
9797 defer {
9898 for (search_dirs.items) |*search_dir| {
99 search_dir.deinit(allocator);
99 search_dir.deinit(allocator, io);
100100 }
101101 search_dirs.deinit(allocator);
102102 }
......@@ -106,13 +106,13 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io
106106 // If dirname returns null, then the root path will be the same as
107107 // the cwd so we don't need to add it as a distinct search path.
108108 if (std.fs.path.dirname(root_path)) |root_dir_path| {
109 var root_dir = try options.cwd.openDir(root_dir_path, .{});
110 errdefer root_dir.close();
109 var root_dir = try options.cwd.openDir(io, root_dir_path, .{});
110 errdefer root_dir.close(io);
111111 try search_dirs.append(allocator, .{ .dir = root_dir, .path = try allocator.dupe(u8, root_dir_path) });
112112 }
113113 }
114 // Re-open the passed in cwd since we want to be able to close it (std.fs.cwd() shouldn't be closed)
115 const cwd_dir = options.cwd.openDir(".", .{}) catch |err| {
114 // Re-open the passed in cwd since we want to be able to close it (Io.Dir.cwd() shouldn't be closed)
115 const cwd_dir = options.cwd.openDir(io, ".", .{}) catch |err| {
116116 try options.diagnostics.append(.{
117117 .err = .failed_to_open_cwd,
118118 .token = .{
......@@ -132,19 +132,19 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io
132132 };
133133 try search_dirs.append(allocator, .{ .dir = cwd_dir, .path = null });
134134 for (options.extra_include_paths) |extra_include_path| {
135 var dir = openSearchPathDir(options.cwd, extra_include_path) catch {
135 var dir = openSearchPathDir(options.cwd, io, extra_include_path) catch {
136136 // TODO: maybe a warning that the search path is skipped?
137137 continue;
138138 };
139 errdefer dir.close();
139 errdefer dir.close(io);
140140 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, extra_include_path) });
141141 }
142142 for (options.system_include_paths) |system_include_path| {
143 var dir = openSearchPathDir(options.cwd, system_include_path) catch {
143 var dir = openSearchPathDir(options.cwd, io, system_include_path) catch {
144144 // TODO: maybe a warning that the search path is skipped?
145145 continue;
146146 };
147 errdefer dir.close();
147 errdefer dir.close(io);
148148 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, system_include_path) });
149149 }
150150 if (!options.ignore_include_env_var) {
......@@ -159,8 +159,8 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io
159159 };
160160 var it = std.mem.tokenizeScalar(u8, INCLUDE, delimiter);
161161 while (it.next()) |search_path| {
162 var dir = openSearchPathDir(options.cwd, search_path) catch continue;
163 errdefer dir.close();
162 var dir = openSearchPathDir(options.cwd, io, search_path) catch continue;
163 errdefer dir.close(io);
164164 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, search_path) });
165165 }
166166 }
......@@ -196,7 +196,7 @@ pub const Compiler = struct {
196196 arena: Allocator,
197197 allocator: Allocator,
198198 io: Io,
199 cwd: std.fs.Dir,
199 cwd: std.Io.Dir,
200200 state: State = .{},
201201 diagnostics: *Diagnostics,
202202 dependencies: ?*Dependencies,
......@@ -388,7 +388,9 @@ pub const Compiler = struct {
388388 /// matching file is invalid. That is, it does not do the `cmd` PATH searching
389389 /// thing of continuing to look for matching files until it finds a valid
390390 /// one if a matching file is invalid.
391 fn searchForFile(self: *Compiler, path: []const u8) !std.fs.File {
391 fn searchForFile(self: *Compiler, path: []const u8) !std.Io.File {
392 const io = self.io;
393
392394 // If the path is absolute, then it is not resolved relative to any search
393395 // paths, so there's no point in checking them.
394396 //
......@@ -404,8 +406,8 @@ pub const Compiler = struct {
404406 // `/test.bin` relative to include paths and instead only treats it as
405407 // an absolute path.
406408 if (std.fs.path.isAbsolute(path)) {
407 const file = try utils.openFileNotDir(std.fs.cwd(), path, .{});
408 errdefer file.close();
409 const file = try utils.openFileNotDir(Io.Dir.cwd(), io, path, .{});
410 errdefer file.close(io);
409411
410412 if (self.dependencies) |dependencies| {
411413 const duped_path = try dependencies.allocator.dupe(u8, path);
......@@ -414,10 +416,10 @@ pub const Compiler = struct {
414416 }
415417 }
416418
417 var first_error: ?(std.fs.File.OpenError || std.fs.File.StatError) = null;
419 var first_error: ?(std.Io.File.OpenError || std.Io.File.StatError) = null;
418420 for (self.search_dirs) |search_dir| {
419 if (utils.openFileNotDir(search_dir.dir, path, .{})) |file| {
420 errdefer file.close();
421 if (utils.openFileNotDir(search_dir.dir, io, path, .{})) |file| {
422 errdefer file.close(io);
421423
422424 if (self.dependencies) |dependencies| {
423425 const searched_file_path = try std.fs.path.join(dependencies.allocator, &.{
......@@ -587,7 +589,7 @@ pub const Compiler = struct {
587589 });
588590 },
589591 };
590 defer file_handle.close();
592 defer file_handle.close(io);
591593 var file_buffer: [2048]u8 = undefined;
592594 var file_reader = file_handle.reader(io, &file_buffer);
593595
......@@ -2892,13 +2894,13 @@ pub const Compiler = struct {
28922894 }
28932895};
28942896
2895pub const OpenSearchPathError = std.fs.Dir.OpenError;
2897pub const OpenSearchPathError = std.Io.Dir.OpenError;
28962898
2897fn openSearchPathDir(dir: std.fs.Dir, path: []const u8) OpenSearchPathError!std.fs.Dir {
2899fn openSearchPathDir(dir: std.Io.Dir, io: Io, path: []const u8) OpenSearchPathError!std.Io.Dir {
28982900 // Validate the search path to avoid possible unreachable on invalid paths,
28992901 // see https://github.com/ziglang/zig/issues/15607 for why this is currently necessary.
29002902 try validateSearchPath(path);
2901 return dir.openDir(path, .{});
2903 return dir.openDir(io, path, .{});
29022904}
29032905
29042906/// Very crude attempt at validating a path. This is imperfect
......@@ -2927,11 +2929,11 @@ fn validateSearchPath(path: []const u8) error{BadPathName}!void {
29272929}
29282930
29292931pub const SearchDir = struct {
2930 dir: std.fs.Dir,
2932 dir: std.Io.Dir,
29312933 path: ?[]const u8,
29322934
2933 pub fn deinit(self: *SearchDir, allocator: Allocator) void {
2934 self.dir.close();
2935 pub fn deinit(self: *SearchDir, allocator: Allocator, io: Io) void {
2936 self.dir.close(io);
29352937 if (self.path) |path| {
29362938 allocator.free(path);
29372939 }
lib/compiler/resinator/errors.zig+45-45
......@@ -67,12 +67,12 @@ pub const Diagnostics = struct {
6767 return @intCast(index);
6868 }
6969
70 pub fn renderToStdErr(self: *Diagnostics, cwd: std.fs.Dir, source: []const u8, source_mappings: ?SourceMappings) void {
70 pub fn renderToStderr(self: *Diagnostics, cwd: Io.Dir, source: []const u8, source_mappings: ?SourceMappings) Io.Cancelable!void {
7171 const io = self.io;
72 const stderr, const ttyconf = std.debug.lockStderrWriter(&.{});
73 defer std.debug.unlockStderrWriter();
72 const stderr = try io.lockStderr(&.{}, null);
73 defer io.unlockStderr();
7474 for (self.errors.items) |err_details| {
75 renderErrorMessage(io, stderr, ttyconf, cwd, err_details, source, self.strings.items, source_mappings) catch return;
75 renderErrorMessage(io, stderr.terminal(), cwd, err_details, source, self.strings.items, source_mappings) catch return;
7676 }
7777 }
7878
......@@ -169,9 +169,9 @@ pub const ErrorDetails = struct {
169169 filename_string_index: FilenameStringIndex,
170170
171171 pub const FilenameStringIndex = std.meta.Int(.unsigned, 32 - @bitSizeOf(FileOpenErrorEnum));
172 pub const FileOpenErrorEnum = std.meta.FieldEnum(std.fs.File.OpenError || std.fs.File.StatError);
172 pub const FileOpenErrorEnum = std.meta.FieldEnum(Io.File.OpenError || Io.File.StatError);
173173
174 pub fn enumFromError(err: (std.fs.File.OpenError || std.fs.File.StatError)) FileOpenErrorEnum {
174 pub fn enumFromError(err: (Io.File.OpenError || Io.File.StatError)) FileOpenErrorEnum {
175175 return switch (err) {
176176 inline else => |e| @field(ErrorDetails.FileOpenError.FileOpenErrorEnum, @errorName(e)),
177177 };
......@@ -901,9 +901,8 @@ const truncated_str = "<...truncated...>";
901901
902902pub fn renderErrorMessage(
903903 io: Io,
904 writer: *std.Io.Writer,
905 tty_config: std.Io.tty.Config,
906 cwd: std.fs.Dir,
904 t: Io.Terminal,
905 cwd: Io.Dir,
907906 err_details: ErrorDetails,
908907 source: []const u8,
909908 strings: []const []const u8,
......@@ -927,36 +926,37 @@ pub fn renderErrorMessage(
927926
928927 const err_line = if (corresponding_span) |span| span.start_line else err_details.token.line_number;
929928
930 try tty_config.setColor(writer, .bold);
929 const writer = t.writer;
930 try t.setColor(.bold);
931931 if (corresponding_file) |file| {
932932 try writer.writeAll(file);
933933 } else {
934 try tty_config.setColor(writer, .dim);
934 try t.setColor(.dim);
935935 try writer.writeAll("<after preprocessor>");
936 try tty_config.setColor(writer, .reset);
937 try tty_config.setColor(writer, .bold);
936 try t.setColor(.reset);
937 try t.setColor(.bold);
938938 }
939939 try writer.print(":{d}:{d}: ", .{ err_line, column });
940940 switch (err_details.type) {
941941 .err => {
942 try tty_config.setColor(writer, .red);
942 try t.setColor(.red);
943943 try writer.writeAll("error: ");
944944 },
945945 .warning => {
946 try tty_config.setColor(writer, .yellow);
946 try t.setColor(.yellow);
947947 try writer.writeAll("warning: ");
948948 },
949949 .note => {
950 try tty_config.setColor(writer, .cyan);
950 try t.setColor(.cyan);
951951 try writer.writeAll("note: ");
952952 },
953953 .hint => unreachable,
954954 }
955 try tty_config.setColor(writer, .reset);
956 try tty_config.setColor(writer, .bold);
955 try t.setColor(.reset);
956 try t.setColor(.bold);
957957 try err_details.render(writer, source, strings);
958958 try writer.writeByte('\n');
959 try tty_config.setColor(writer, .reset);
959 try t.setColor(.reset);
960960
961961 if (!err_details.print_source_line) {
962962 try writer.writeByte('\n');
......@@ -983,20 +983,20 @@ pub fn renderErrorMessage(
983983
984984 try writer.writeAll(source_line_for_display.line);
985985 if (source_line_for_display.truncated) {
986 try tty_config.setColor(writer, .dim);
986 try t.setColor(.dim);
987987 try writer.writeAll(truncated_str);
988 try tty_config.setColor(writer, .reset);
988 try t.setColor(.reset);
989989 }
990990 try writer.writeByte('\n');
991991
992 try tty_config.setColor(writer, .green);
992 try t.setColor(.green);
993993 const num_spaces = truncated_visual_info.point_offset - truncated_visual_info.before_len;
994994 try writer.splatByteAll(' ', num_spaces);
995995 try writer.splatByteAll('~', truncated_visual_info.before_len);
996996 try writer.writeByte('^');
997997 try writer.splatByteAll('~', truncated_visual_info.after_len);
998998 try writer.writeByte('\n');
999 try tty_config.setColor(writer, .reset);
999 try t.setColor(.reset);
10001000
10011001 if (corresponding_span != null and corresponding_file != null) {
10021002 var worth_printing_lines: bool = true;
......@@ -1021,22 +1021,22 @@ pub fn renderErrorMessage(
10211021 break :blk null;
10221022 },
10231023 };
1024 defer if (corresponding_lines) |*cl| cl.deinit();
1024 defer if (corresponding_lines) |*cl| cl.deinit(io);
10251025
1026 try tty_config.setColor(writer, .bold);
1026 try t.setColor(.bold);
10271027 if (corresponding_file) |file| {
10281028 try writer.writeAll(file);
10291029 } else {
1030 try tty_config.setColor(writer, .dim);
1030 try t.setColor(.dim);
10311031 try writer.writeAll("<after preprocessor>");
1032 try tty_config.setColor(writer, .reset);
1033 try tty_config.setColor(writer, .bold);
1032 try t.setColor(.reset);
1033 try t.setColor(.bold);
10341034 }
10351035 try writer.print(":{d}:{d}: ", .{ err_line, column });
1036 try tty_config.setColor(writer, .cyan);
1036 try t.setColor(.cyan);
10371037 try writer.writeAll("note: ");
1038 try tty_config.setColor(writer, .reset);
1039 try tty_config.setColor(writer, .bold);
1038 try t.setColor(.reset);
1039 try t.setColor(.bold);
10401040 try writer.writeAll("this line originated from line");
10411041 if (corresponding_span.?.start_line != corresponding_span.?.end_line) {
10421042 try writer.print("s {}-{}", .{ corresponding_span.?.start_line, corresponding_span.?.end_line });
......@@ -1044,7 +1044,7 @@ pub fn renderErrorMessage(
10441044 try writer.print(" {}", .{corresponding_span.?.start_line});
10451045 }
10461046 try writer.print(" of file '{s}'\n", .{corresponding_file.?});
1047 try tty_config.setColor(writer, .reset);
1047 try t.setColor(.reset);
10481048
10491049 if (!worth_printing_lines) return;
10501050
......@@ -1055,21 +1055,21 @@ pub fn renderErrorMessage(
10551055 }) |display_line| {
10561056 try writer.writeAll(display_line.line);
10571057 if (display_line.truncated) {
1058 try tty_config.setColor(writer, .dim);
1058 try t.setColor(.dim);
10591059 try writer.writeAll(truncated_str);
1060 try tty_config.setColor(writer, .reset);
1060 try t.setColor(.reset);
10611061 }
10621062 try writer.writeByte('\n');
10631063 }
10641064 break :write_lines null;
10651065 };
10661066 if (write_lines_err) |err| {
1067 try tty_config.setColor(writer, .red);
1067 try t.setColor(.red);
10681068 try writer.writeAll(" | ");
1069 try tty_config.setColor(writer, .reset);
1070 try tty_config.setColor(writer, .dim);
1069 try t.setColor(.reset);
1070 try t.setColor(.dim);
10711071 try writer.print("unable to print line(s) from file: {s}\n", .{@errorName(err)});
1072 try tty_config.setColor(writer, .reset);
1072 try t.setColor(.reset);
10731073 }
10741074 try writer.writeByte('\n');
10751075 }
......@@ -1094,13 +1094,13 @@ const CorrespondingLines = struct {
10941094 last_byte: u8 = 0,
10951095 at_eof: bool = false,
10961096 span: SourceMappings.CorrespondingSpan,
1097 file: std.fs.File,
1098 file_reader: std.fs.File.Reader,
1097 file: Io.File,
1098 file_reader: Io.File.Reader,
10991099 code_page: SupportedCodePage,
11001100
11011101 pub fn init(
11021102 io: Io,
1103 cwd: std.fs.Dir,
1103 cwd: Io.Dir,
11041104 err_details: ErrorDetails,
11051105 line_for_comparison: []const u8,
11061106 corresponding_span: SourceMappings.CorrespondingSpan,
......@@ -1120,12 +1120,12 @@ const CorrespondingLines = struct {
11201120
11211121 var corresponding_lines = CorrespondingLines{
11221122 .span = corresponding_span,
1123 .file = try utils.openFileNotDir(cwd, corresponding_file, .{}),
1123 .file = try utils.openFileNotDir(cwd, io, corresponding_file, .{}),
11241124 .code_page = err_details.code_page,
11251125 .file_reader = undefined,
11261126 };
11271127 corresponding_lines.file_reader = corresponding_lines.file.reader(io, file_reader_buf);
1128 errdefer corresponding_lines.deinit();
1128 errdefer corresponding_lines.deinit(io);
11291129
11301130 try corresponding_lines.writeLineFromStreamVerbatim(
11311131 &corresponding_lines.file_reader.interface,
......@@ -1221,8 +1221,8 @@ const CorrespondingLines = struct {
12211221 };
12221222 }
12231223
1224 pub fn deinit(self: *CorrespondingLines) void {
1225 self.file.close();
1224 pub fn deinit(self: *CorrespondingLines, io: Io) void {
1225 self.file.close(io);
12261226 }
12271227};
12281228
lib/compiler/resinator/main.zig+94-94
......@@ -24,6 +24,10 @@ pub fn main() !void {
2424 defer std.debug.assert(debug_allocator.deinit() == .ok);
2525 const gpa = debug_allocator.allocator();
2626
27 var threaded: std.Io.Threaded = .init(gpa, .{});
28 defer threaded.deinit();
29 const io = threaded.io();
30
2731 var arena_state = std.heap.ArenaAllocator.init(gpa);
2832 defer arena_state.deinit();
2933 const arena = arena_state.allocator();
......@@ -31,8 +35,8 @@ pub fn main() !void {
3135 const args = try std.process.argsAlloc(arena);
3236
3337 if (args.len < 2) {
34 const w, const ttyconf = std.debug.lockStderrWriter(&.{});
35 try renderErrorMessage(w, ttyconf, .err, "expected zig lib dir as first argument", .{});
38 const stderr = try io.lockStderr(&.{}, null);
39 try renderErrorMessage(stderr.terminal(), .err, "expected zig lib dir as first argument", .{});
3640 std.process.exit(1);
3741 }
3842 const zig_lib_dir = args[1];
......@@ -45,7 +49,7 @@ pub fn main() !void {
4549 }
4650
4751 var stdout_buffer: [1024]u8 = undefined;
48 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
52 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
4953 const stdout = &stdout_writer.interface;
5054 var error_handler: ErrorHandler = switch (zig_integration) {
5155 true => .{
......@@ -60,35 +64,31 @@ pub fn main() !void {
6064 var options = options: {
6165 var cli_diagnostics = cli.Diagnostics.init(gpa);
6266 defer cli_diagnostics.deinit();
63 var options = cli.parse(gpa, cli_args, &cli_diagnostics) catch |err| switch (err) {
67 var options = cli.parse(gpa, io, cli_args, &cli_diagnostics) catch |err| switch (err) {
6468 error.ParseError => {
65 try error_handler.emitCliDiagnostics(gpa, cli_args, &cli_diagnostics);
69 try error_handler.emitCliDiagnostics(gpa, io, cli_args, &cli_diagnostics);
6670 std.process.exit(1);
6771 },
6872 else => |e| return e,
6973 };
70 try options.maybeAppendRC(std.fs.cwd());
74 try options.maybeAppendRC(io, Io.Dir.cwd());
7175
7276 if (!zig_integration) {
7377 // print any warnings/notes
74 cli_diagnostics.renderToStdErr(cli_args);
78 try cli_diagnostics.renderToStderr(io, cli_args);
7579 // If there was something printed, then add an extra newline separator
7680 // so that there is a clear separation between the cli diagnostics and whatever
7781 // gets printed after
7882 if (cli_diagnostics.errors.items.len > 0) {
79 const stderr, _ = std.debug.lockStderrWriter(&.{});
80 defer std.debug.unlockStderrWriter();
81 try stderr.writeByte('\n');
83 const stderr = try io.lockStderr(&.{}, null);
84 defer io.unlockStderr();
85 try stderr.file_writer.interface.writeByte('\n');
8286 }
8387 }
8488 break :options options;
8589 };
8690 defer options.deinit();
8791
88 var threaded: std.Io.Threaded = .init(gpa);
89 defer threaded.deinit();
90 const io = threaded.io();
91
9292 if (options.print_help_and_exit) {
9393 try cli.writeUsage(stdout, "zig rc");
9494 try stdout.flush();
......@@ -130,18 +130,15 @@ pub fn main() !void {
130130 var stderr_buf: [512]u8 = undefined;
131131 var diagnostics: aro.Diagnostics = .{ .output = output: {
132132 if (zig_integration) break :output .{ .to_list = .{ .arena = .init(gpa) } };
133 const w, const ttyconf = std.debug.lockStderrWriter(&stderr_buf);
134 break :output .{ .to_writer = .{
135 .writer = w,
136 .color = ttyconf,
137 } };
133 const stderr = try io.lockStderr(&stderr_buf, null);
134 break :output .{ .to_writer = stderr.terminal() };
138135 } };
139136 defer {
140137 diagnostics.deinit();
141 if (!zig_integration) std.debug.unlockStderrWriter();
138 if (!zig_integration) std.debug.unlockStderr();
142139 }
143140
144 var comp = aro.Compilation.init(aro_arena, aro_arena, io, &diagnostics, std.fs.cwd());
141 var comp = aro.Compilation.init(aro_arena, aro_arena, io, &diagnostics, Io.Dir.cwd());
145142 defer comp.deinit();
146143
147144 var argv: std.ArrayList([]const u8) = .empty;
......@@ -175,11 +172,11 @@ pub fn main() !void {
175172 std.process.exit(1);
176173 },
177174 error.FileTooBig => {
178 try error_handler.emitMessage(gpa, .err, "failed during preprocessing: maximum file size exceeded", .{});
175 try error_handler.emitMessage(gpa, io, .err, "failed during preprocessing: maximum file size exceeded", .{});
179176 std.process.exit(1);
180177 },
181178 error.WriteFailed => {
182 try error_handler.emitMessage(gpa, .err, "failed during preprocessing: error writing the preprocessed output", .{});
179 try error_handler.emitMessage(gpa, io, .err, "failed during preprocessing: error writing the preprocessed output", .{});
183180 std.process.exit(1);
184181 },
185182 error.OutOfMemory => |e| return e,
......@@ -191,13 +188,13 @@ pub fn main() !void {
191188 .stdio => |file| {
192189 var file_reader = file.reader(io, &.{});
193190 break :full_input file_reader.interface.allocRemaining(gpa, .unlimited) catch |err| {
194 try error_handler.emitMessage(gpa, .err, "unable to read input from stdin: {s}", .{@errorName(err)});
191 try error_handler.emitMessage(gpa, io, .err, "unable to read input from stdin: {s}", .{@errorName(err)});
195192 std.process.exit(1);
196193 };
197194 },
198195 .filename => |input_filename| {
199 break :full_input std.fs.cwd().readFileAlloc(input_filename, gpa, .unlimited) catch |err| {
200 try error_handler.emitMessage(gpa, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) });
196 break :full_input Io.Dir.cwd().readFileAlloc(io, input_filename, gpa, .unlimited) catch |err| {
197 try error_handler.emitMessage(gpa, io, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) });
201198 std.process.exit(1);
202199 };
203200 },
......@@ -209,10 +206,10 @@ pub fn main() !void {
209206 if (options.preprocess == .only) {
210207 switch (options.output_source) {
211208 .stdio => |output_file| {
212 try output_file.writeAll(full_input);
209 try output_file.writeStreamingAll(io, full_input);
213210 },
214211 .filename => |output_filename| {
215 try std.fs.cwd().writeFile(.{ .sub_path = output_filename, .data = full_input });
212 try Io.Dir.cwd().writeFile(io, .{ .sub_path = output_filename, .data = full_input });
216213 },
217214 }
218215 return;
......@@ -227,16 +224,16 @@ pub fn main() !void {
227224 .source = .{ .memory = .empty },
228225 }
229226 else if (options.input_format == .res)
230 IoStream.fromIoSource(options.input_source, .input) catch |err| {
231 try error_handler.emitMessage(gpa, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) });
227 IoStream.fromIoSource(io, options.input_source, .input) catch |err| {
228 try error_handler.emitMessage(gpa, io, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) });
232229 std.process.exit(1);
233230 }
234231 else
235 IoStream.fromIoSource(options.output_source, .output) catch |err| {
236 try error_handler.emitMessage(gpa, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
232 IoStream.fromIoSource(io, options.output_source, .output) catch |err| {
233 try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
237234 std.process.exit(1);
238235 };
239 defer res_stream.deinit(gpa);
236 defer res_stream.deinit(gpa, io);
240237
241238 const res_data = res_data: {
242239 if (options.input_format != .res) {
......@@ -246,17 +243,17 @@ pub fn main() !void {
246243 var mapping_results = parseAndRemoveLineCommands(gpa, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) {
247244 error.InvalidLineCommand => {
248245 // TODO: Maybe output the invalid line command
249 try error_handler.emitMessage(gpa, .err, "invalid line command in the preprocessed source", .{});
246 try error_handler.emitMessage(gpa, io, .err, "invalid line command in the preprocessed source", .{});
250247 if (options.preprocess == .no) {
251 try error_handler.emitMessage(gpa, .note, "line commands must be of the format: #line <num> \"<path>\"", .{});
248 try error_handler.emitMessage(gpa, io, .note, "line commands must be of the format: #line <num> \"<path>\"", .{});
252249 } else {
253 try error_handler.emitMessage(gpa, .note, "this is likely to be a bug, please report it", .{});
250 try error_handler.emitMessage(gpa, io, .note, "this is likely to be a bug, please report it", .{});
254251 }
255252 std.process.exit(1);
256253 },
257254 error.LineNumberOverflow => {
258255 // TODO: Better error message
259 try error_handler.emitMessage(gpa, .err, "line number count exceeded maximum of {}", .{std.math.maxInt(usize)});
256 try error_handler.emitMessage(gpa, io, .err, "line number count exceeded maximum of {}", .{std.math.maxInt(usize)});
260257 std.process.exit(1);
261258 },
262259 error.OutOfMemory => |e| return e,
......@@ -272,12 +269,12 @@ pub fn main() !void {
272269 defer diagnostics.deinit();
273270
274271 var output_buffer: [4096]u8 = undefined;
275 var res_stream_writer = res_stream.source.writer(gpa, &output_buffer);
272 var res_stream_writer = res_stream.source.writer(gpa, io, &output_buffer);
276273 defer res_stream_writer.deinit(&res_stream.source);
277274 const output_buffered_stream = res_stream_writer.interface();
278275
279276 compile(gpa, io, final_input, output_buffered_stream, .{
280 .cwd = std.fs.cwd(),
277 .cwd = Io.Dir.cwd(),
281278 .diagnostics = &diagnostics,
282279 .source_mappings = &mapping_results.mappings,
283280 .dependencies = maybe_dependencies,
......@@ -294,9 +291,9 @@ pub fn main() !void {
294291 .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,
295292 }) catch |err| switch (err) {
296293 error.ParseError, error.CompileError => {
297 try error_handler.emitDiagnostics(gpa, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings);
294 try error_handler.emitDiagnostics(gpa, Io.Dir.cwd(), final_input, &diagnostics, mapping_results.mappings);
298295 // Delete the output file on error
299 res_stream.cleanupAfterError();
296 res_stream.cleanupAfterError(io);
300297 std.process.exit(1);
301298 },
302299 else => |e| return e,
......@@ -306,19 +303,19 @@ pub fn main() !void {
306303
307304 // print any warnings/notes
308305 if (!zig_integration) {
309 diagnostics.renderToStdErr(std.fs.cwd(), final_input, mapping_results.mappings);
306 try diagnostics.renderToStderr(Io.Dir.cwd(), final_input, mapping_results.mappings);
310307 }
311308
312309 // write the depfile
313310 if (options.depfile_path) |depfile_path| {
314 var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| {
315 try error_handler.emitMessage(gpa, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
311 var depfile = Io.Dir.cwd().createFile(io, depfile_path, .{}) catch |err| {
312 try error_handler.emitMessage(gpa, io, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
316313 std.process.exit(1);
317314 };
318 defer depfile.close();
315 defer depfile.close(io);
319316
320317 var depfile_buffer: [1024]u8 = undefined;
321 var depfile_writer = depfile.writer(&depfile_buffer);
318 var depfile_writer = depfile.writer(io, &depfile_buffer);
322319 switch (options.depfile_fmt) {
323320 .json => {
324321 var write_stream: std.json.Stringify = .{
......@@ -340,7 +337,7 @@ pub fn main() !void {
340337 if (options.output_format != .coff) return;
341338
342339 break :res_data res_stream.source.readAll(gpa, io) catch |err| {
343 try error_handler.emitMessage(gpa, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
340 try error_handler.emitMessage(gpa, io, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
344341 std.process.exit(1);
345342 };
346343 };
......@@ -353,27 +350,27 @@ pub fn main() !void {
353350 var res_reader: std.Io.Reader = .fixed(res_data.bytes);
354351 break :resources cvtres.parseRes(gpa, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| {
355352 // TODO: Better errors
356 try error_handler.emitMessage(gpa, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
353 try error_handler.emitMessage(gpa, io, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
357354 std.process.exit(1);
358355 };
359356 };
360357 defer resources.deinit();
361358
362 var coff_stream = IoStream.fromIoSource(options.output_source, .output) catch |err| {
363 try error_handler.emitMessage(gpa, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
359 var coff_stream = IoStream.fromIoSource(io, options.output_source, .output) catch |err| {
360 try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
364361 std.process.exit(1);
365362 };
366 defer coff_stream.deinit(gpa);
363 defer coff_stream.deinit(gpa, io);
367364
368365 var coff_output_buffer: [4096]u8 = undefined;
369 var coff_output_buffered_stream = coff_stream.source.writer(gpa, &coff_output_buffer);
366 var coff_output_buffered_stream = coff_stream.source.writer(gpa, io, &coff_output_buffer);
370367
371368 var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} };
372369 cvtres.writeCoff(gpa, coff_output_buffered_stream.interface(), resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| {
373370 switch (err) {
374371 error.DuplicateResource => {
375372 const duplicate_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
376 try error_handler.emitMessage(gpa, .err, "duplicate resource [id: {f}, type: {f}, language: {f}]", .{
373 try error_handler.emitMessage(gpa, io, .err, "duplicate resource [id: {f}, type: {f}, language: {f}]", .{
377374 duplicate_resource.name_value,
378375 fmtResourceType(duplicate_resource.type_value),
379376 duplicate_resource.language,
......@@ -381,8 +378,8 @@ pub fn main() !void {
381378 },
382379 error.ResourceDataTooLong => {
383380 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
384 try error_handler.emitMessage(gpa, .err, "resource has a data length that is too large to be written into a coff section", .{});
385 try error_handler.emitMessage(gpa, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{
381 try error_handler.emitMessage(gpa, io, .err, "resource has a data length that is too large to be written into a coff section", .{});
382 try error_handler.emitMessage(gpa, io, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{
386383 overflow_resource.name_value,
387384 fmtResourceType(overflow_resource.type_value),
388385 overflow_resource.language,
......@@ -390,19 +387,19 @@ pub fn main() !void {
390387 },
391388 error.TotalResourceDataTooLong => {
392389 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
393 try error_handler.emitMessage(gpa, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{});
394 try error_handler.emitMessage(gpa, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{
390 try error_handler.emitMessage(gpa, io, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{});
391 try error_handler.emitMessage(gpa, io, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{
395392 overflow_resource.name_value,
396393 fmtResourceType(overflow_resource.type_value),
397394 overflow_resource.language,
398395 });
399396 },
400397 else => {
401 try error_handler.emitMessage(gpa, .err, "unable to write coff output file '{s}': {s}", .{ coff_stream.name, @errorName(err) });
398 try error_handler.emitMessage(gpa, io, .err, "unable to write coff output file '{s}': {s}", .{ coff_stream.name, @errorName(err) });
402399 },
403400 }
404401 // Delete the output file on error
405 coff_stream.cleanupAfterError();
402 coff_stream.cleanupAfterError(io);
406403 std.process.exit(1);
407404 };
408405
......@@ -416,58 +413,58 @@ const IoStream = struct {
416413
417414 pub const IoDirection = enum { input, output };
418415
419 pub fn fromIoSource(source: cli.Options.IoSource, io: IoDirection) !IoStream {
416 pub fn fromIoSource(io: Io, source: cli.Options.IoSource, io_direction: IoDirection) !IoStream {
420417 return .{
421418 .name = switch (source) {
422419 .filename => |filename| filename,
423 .stdio => switch (io) {
420 .stdio => switch (io_direction) {
424421 .input => "<stdin>",
425422 .output => "<stdout>",
426423 },
427424 },
428425 .intermediate = false,
429 .source = try Source.fromIoSource(source, io),
426 .source = try Source.fromIoSource(io, source, io_direction),
430427 };
431428 }
432429
433 pub fn deinit(self: *IoStream, allocator: Allocator) void {
434 self.source.deinit(allocator);
430 pub fn deinit(self: *IoStream, allocator: Allocator, io: Io) void {
431 self.source.deinit(allocator, io);
435432 }
436433
437 pub fn cleanupAfterError(self: *IoStream) void {
434 pub fn cleanupAfterError(self: *IoStream, io: Io) void {
438435 switch (self.source) {
439436 .file => |file| {
440437 // Delete the output file on error
441 file.close();
438 file.close(io);
442439 // Failing to delete is not really a big deal, so swallow any errors
443 std.fs.cwd().deleteFile(self.name) catch {};
440 Io.Dir.cwd().deleteFile(io, self.name) catch {};
444441 },
445442 .stdio, .memory, .closed => return,
446443 }
447444 }
448445
449446 pub const Source = union(enum) {
450 file: std.fs.File,
451 stdio: std.fs.File,
447 file: Io.File,
448 stdio: Io.File,
452449 memory: std.ArrayList(u8),
453450 /// The source has been closed and any usage of the Source in this state is illegal (except deinit).
454451 closed: void,
455452
456 pub fn fromIoSource(source: cli.Options.IoSource, io: IoDirection) !Source {
453 pub fn fromIoSource(io: Io, source: cli.Options.IoSource, io_direction: IoDirection) !Source {
457454 switch (source) {
458455 .filename => |filename| return .{
459 .file = switch (io) {
460 .input => try openFileNotDir(std.fs.cwd(), filename, .{}),
461 .output => try std.fs.cwd().createFile(filename, .{}),
456 .file = switch (io_direction) {
457 .input => try openFileNotDir(Io.Dir.cwd(), io, filename, .{}),
458 .output => try Io.Dir.cwd().createFile(io, filename, .{}),
462459 },
463460 },
464461 .stdio => |file| return .{ .stdio = file },
465462 }
466463 }
467464
468 pub fn deinit(self: *Source, allocator: Allocator) void {
465 pub fn deinit(self: *Source, allocator: Allocator, io: Io) void {
469466 switch (self.*) {
470 .file => |file| file.close(),
467 .file => |file| file.close(io),
471468 .stdio => {},
472469 .memory => |*list| list.deinit(allocator),
473470 .closed => {},
......@@ -500,10 +497,10 @@ const IoStream = struct {
500497 }
501498
502499 pub const Writer = union(enum) {
503 file: std.fs.File.Writer,
500 file: Io.File.Writer,
504501 allocating: std.Io.Writer.Allocating,
505502
506 pub const Error = Allocator.Error || std.fs.File.WriteError;
503 pub const Error = Allocator.Error || Io.File.WriteError;
507504
508505 pub fn interface(this: *@This()) *std.Io.Writer {
509506 return switch (this.*) {
......@@ -521,9 +518,9 @@ const IoStream = struct {
521518 }
522519 };
523520
524 pub fn writer(source: *Source, allocator: Allocator, buffer: []u8) Writer {
521 pub fn writer(source: *Source, allocator: Allocator, io: Io, buffer: []u8) Writer {
525522 return switch (source.*) {
526 .file, .stdio => |file| .{ .file = file.writer(buffer) },
523 .file, .stdio => |file| .{ .file = file.writer(io, buffer) },
527524 .memory => |*list| .{ .allocating = .fromArrayList(allocator, list) },
528525 .closed => unreachable,
529526 };
......@@ -550,16 +547,16 @@ const LazyIncludePaths = struct {
550547 else => |e| {
551548 switch (e) {
552549 error.UnsupportedAutoIncludesMachineType => {
553 try error_handler.emitMessage(self.arena, .err, "automatic include path detection is not supported for target '{s}'", .{@tagName(self.target_machine_type)});
550 try error_handler.emitMessage(self.arena, io, .err, "automatic include path detection is not supported for target '{s}'", .{@tagName(self.target_machine_type)});
554551 },
555552 error.MsvcIncludesNotFound => {
556 try error_handler.emitMessage(self.arena, .err, "MSVC include paths could not be automatically detected", .{});
553 try error_handler.emitMessage(self.arena, io, .err, "MSVC include paths could not be automatically detected", .{});
557554 },
558555 error.MingwIncludesNotFound => {
559 try error_handler.emitMessage(self.arena, .err, "MinGW include paths could not be automatically detected", .{});
556 try error_handler.emitMessage(self.arena, io, .err, "MinGW include paths could not be automatically detected", .{});
560557 },
561558 }
562 try error_handler.emitMessage(self.arena, .note, "to disable auto includes, use the option /:auto-includes none", .{});
559 try error_handler.emitMessage(self.arena, io, .note, "to disable auto includes, use the option /:auto-includes none", .{});
563560 std.process.exit(1);
564561 },
565562 };
......@@ -618,7 +615,7 @@ fn getIncludePaths(
618615 };
619616 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
620617 const is_native_abi = target_query.isNativeAbi();
621 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch {
618 const detected_libc = std.zig.LibCDirs.detect(arena, io, zig_lib_dir, &target, is_native_abi, true, null) catch {
622619 if (includes == .any) {
623620 // fall back to mingw
624621 includes = .gnu;
......@@ -644,7 +641,7 @@ fn getIncludePaths(
644641 };
645642 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
646643 const is_native_abi = target_query.isNativeAbi();
647 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) {
644 const detected_libc = std.zig.LibCDirs.detect(arena, io, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) {
648645 error.OutOfMemory => |e| return e,
649646 else => return error.MingwIncludesNotFound,
650647 };
......@@ -664,6 +661,7 @@ const ErrorHandler = union(enum) {
664661 pub fn emitCliDiagnostics(
665662 self: *ErrorHandler,
666663 allocator: Allocator,
664 io: Io,
667665 args: []const []const u8,
668666 diagnostics: *cli.Diagnostics,
669667 ) !void {
......@@ -674,7 +672,7 @@ const ErrorHandler = union(enum) {
674672
675673 try server.serveErrorBundle(error_bundle);
676674 },
677 .stderr => diagnostics.renderToStdErr(args),
675 .stderr => return diagnostics.renderToStderr(io, args),
678676 }
679677 }
680678
......@@ -684,6 +682,7 @@ const ErrorHandler = union(enum) {
684682 fail_msg: []const u8,
685683 comp: *aro.Compilation,
686684 ) !void {
685 const io = comp.io;
687686 switch (self.*) {
688687 .server => |*server| {
689688 var error_bundle = try compiler_util.aroDiagnosticsToErrorBundle(
......@@ -697,9 +696,9 @@ const ErrorHandler = union(enum) {
697696 },
698697 .stderr => {
699698 // aro errors have already been emitted
700 const stderr, const ttyconf = std.debug.lockStderrWriter(&.{});
701 defer std.debug.unlockStderrWriter();
702 try renderErrorMessage(stderr, ttyconf, .err, "{s}", .{fail_msg});
699 const stderr = try io.lockStderr(&.{}, null);
700 defer io.unlockStderr();
701 try renderErrorMessage(stderr.terminal(), .err, "{s}", .{fail_msg});
703702 },
704703 }
705704 }
......@@ -707,7 +706,7 @@ const ErrorHandler = union(enum) {
707706 pub fn emitDiagnostics(
708707 self: *ErrorHandler,
709708 allocator: Allocator,
710 cwd: std.fs.Dir,
709 cwd: Io.Dir,
711710 source: []const u8,
712711 diagnostics: *Diagnostics,
713712 mappings: SourceMappings,
......@@ -719,13 +718,14 @@ const ErrorHandler = union(enum) {
719718
720719 try server.serveErrorBundle(error_bundle);
721720 },
722 .stderr => diagnostics.renderToStdErr(cwd, source, mappings),
721 .stderr => return diagnostics.renderToStderr(cwd, source, mappings),
723722 }
724723 }
725724
726725 pub fn emitMessage(
727726 self: *ErrorHandler,
728727 allocator: Allocator,
728 io: Io,
729729 msg_type: @import("utils.zig").ErrorMessageType,
730730 comptime format: []const u8,
731731 args: anytype,
......@@ -741,9 +741,9 @@ const ErrorHandler = union(enum) {
741741 try server.serveErrorBundle(error_bundle);
742742 },
743743 .stderr => {
744 const stderr, const ttyconf = std.debug.lockStderrWriter(&.{});
745 defer std.debug.unlockStderrWriter();
746 try renderErrorMessage(stderr, ttyconf, msg_type, format, args);
744 const stderr = try io.lockStderr(&.{}, null);
745 defer io.unlockStderr();
746 try renderErrorMessage(stderr.terminal(), msg_type, format, args);
747747 },
748748 }
749749 }
lib/compiler/resinator/utils.zig+22-18
......@@ -1,6 +1,8 @@
1const std = @import("std");
21const builtin = @import("builtin");
32
3const std = @import("std");
4const Io = std.Io;
5
46pub const UncheckedSliceWriter = struct {
57 const Self = @This();
68
......@@ -23,19 +25,20 @@ pub const UncheckedSliceWriter = struct {
2325 }
2426};
2527
26/// Cross-platform 'std.fs.Dir.openFile' wrapper that will always return IsDir if
28/// Cross-platform 'Io.Dir.openFile' wrapper that will always return IsDir if
2729/// a directory is attempted to be opened.
2830/// TODO: Remove once https://github.com/ziglang/zig/issues/5732 is addressed.
2931pub fn openFileNotDir(
30 cwd: std.fs.Dir,
32 cwd: Io.Dir,
33 io: Io,
3134 path: []const u8,
32 flags: std.fs.File.OpenFlags,
33) (std.fs.File.OpenError || std.fs.File.StatError)!std.fs.File {
34 const file = try cwd.openFile(path, flags);
35 errdefer file.close();
35 flags: Io.File.OpenFlags,
36) (Io.File.OpenError || Io.File.StatError)!Io.File {
37 const file = try cwd.openFile(io, path, flags);
38 errdefer file.close(io);
3639 // https://github.com/ziglang/zig/issues/5732
3740 if (builtin.os.tag != .windows) {
38 const stat = try file.stat();
41 const stat = try file.stat(io);
3942
4043 if (stat.kind == .directory)
4144 return error.IsDir;
......@@ -89,31 +92,32 @@ pub const ErrorMessageType = enum { err, warning, note };
8992
9093/// Used for generic colored errors/warnings/notes, more context-specific error messages
9194/// are handled elsewhere.
92pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, msg_type: ErrorMessageType, comptime format: []const u8, args: anytype) !void {
95pub fn renderErrorMessage(t: Io.Terminal, msg_type: ErrorMessageType, comptime format: []const u8, args: anytype) !void {
96 const writer = t.writer;
9397 switch (msg_type) {
9498 .err => {
95 try config.setColor(writer, .bold);
96 try config.setColor(writer, .red);
99 try t.setColor(.bold);
100 try t.setColor(.red);
97101 try writer.writeAll("error: ");
98102 },
99103 .warning => {
100 try config.setColor(writer, .bold);
101 try config.setColor(writer, .yellow);
104 try t.setColor(.bold);
105 try t.setColor(.yellow);
102106 try writer.writeAll("warning: ");
103107 },
104108 .note => {
105 try config.setColor(writer, .reset);
106 try config.setColor(writer, .cyan);
109 try t.setColor(.reset);
110 try t.setColor(.cyan);
107111 try writer.writeAll("note: ");
108112 },
109113 }
110 try config.setColor(writer, .reset);
114 try t.setColor(.reset);
111115 if (msg_type == .err) {
112 try config.setColor(writer, .bold);
116 try t.setColor(.bold);
113117 }
114118 try writer.print(format, args);
115119 try writer.writeByte('\n');
116 try config.setColor(writer, .reset);
120 try t.setColor(.reset);
117121}
118122
119123pub fn isLineEndingPair(first: u8, second: u8) bool {
lib/compiler/std-docs.zig+70-57
......@@ -1,12 +1,14 @@
11const builtin = @import("builtin");
2
23const std = @import("std");
4const Io = std.Io;
35const mem = std.mem;
46const Allocator = std.mem.Allocator;
57const assert = std.debug.assert;
68const Cache = std.Build.Cache;
79
8fn usage() noreturn {
9 std.fs.File.stdout().writeAll(
10fn usage(io: Io) noreturn {
11 Io.File.stdout().writeStreamingAll(io,
1012 \\Usage: zig std [options]
1113 \\
1214 \\Options:
......@@ -27,6 +29,10 @@ pub fn main() !void {
2729 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
2830 const gpa = general_purpose_allocator.allocator();
2931
32 var threaded: Io.Threaded = .init(gpa, .{});
33 defer threaded.deinit();
34 const io = threaded.io();
35
3036 var argv = try std.process.argsWithAllocator(arena);
3137 defer argv.deinit();
3238 assert(argv.skip());
......@@ -34,18 +40,18 @@ pub fn main() !void {
3440 const zig_exe_path = argv.next().?;
3541 const global_cache_path = argv.next().?;
3642
37 var lib_dir = try std.fs.cwd().openDir(zig_lib_directory, .{});
38 defer lib_dir.close();
43 var lib_dir = try Io.Dir.cwd().openDir(io, zig_lib_directory, .{});
44 defer lib_dir.close(io);
3945
4046 var listen_port: u16 = 0;
4147 var force_open_browser: ?bool = null;
4248 while (argv.next()) |arg| {
4349 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
44 usage();
50 usage(io);
4551 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--port")) {
46 listen_port = std.fmt.parseInt(u16, argv.next() orelse usage(), 10) catch |err| {
52 listen_port = std.fmt.parseInt(u16, argv.next() orelse usage(io), 10) catch |err| {
4753 std.log.err("expected port number: {}", .{err});
48 usage();
54 usage(io);
4955 };
5056 } else if (mem.eql(u8, arg, "--open-browser")) {
5157 force_open_browser = true;
......@@ -53,69 +59,70 @@ pub fn main() !void {
5359 force_open_browser = false;
5460 } else {
5561 std.log.err("unrecognized argument: {s}", .{arg});
56 usage();
62 usage(io);
5763 }
5864 }
5965 const should_open_browser = force_open_browser orelse (listen_port == 0);
6066
61 const address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable;
62 var http_server = try address.listen(.{
67 const address = Io.net.IpAddress.parse("127.0.0.1", listen_port) catch unreachable;
68 var http_server = try address.listen(io, .{
6369 .reuse_address = true,
6470 });
65 const port = http_server.listen_address.in.getPort();
71 const port = http_server.socket.address.getPort();
6672 const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port});
67 std.fs.File.stdout().writeAll(url_with_newline) catch {};
73 Io.File.stdout().writeStreamingAll(io, url_with_newline) catch {};
6874 if (should_open_browser) {
69 openBrowserTab(gpa, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| {
70 std.log.err("unable to open browser: {s}", .{@errorName(err)});
75 openBrowserTab(gpa, io, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| {
76 std.log.err("unable to open browser: {t}", .{err});
7177 };
7278 }
7379
7480 var context: Context = .{
7581 .gpa = gpa,
82 .io = io,
7683 .zig_exe_path = zig_exe_path,
7784 .global_cache_path = global_cache_path,
7885 .lib_dir = lib_dir,
7986 .zig_lib_directory = zig_lib_directory,
8087 };
8188
89 var group: Io.Group = .init;
90 defer group.cancel(io);
91
8292 while (true) {
83 const connection = try http_server.accept();
84 _ = std.Thread.spawn(.{}, accept, .{ &context, connection }) catch |err| {
85 std.log.err("unable to accept connection: {s}", .{@errorName(err)});
86 connection.stream.close();
87 continue;
88 };
93 const stream = try http_server.accept(io);
94 group.async(io, accept, .{ &context, stream });
8995 }
9096}
9197
92fn accept(context: *Context, connection: std.net.Server.Connection) void {
93 defer connection.stream.close();
98fn accept(context: *Context, stream: Io.net.Stream) void {
99 const io = context.io;
100 defer stream.close(io);
94101
95102 var recv_buffer: [4000]u8 = undefined;
96103 var send_buffer: [4000]u8 = undefined;
97 var conn_reader = connection.stream.reader(&recv_buffer);
98 var conn_writer = connection.stream.writer(&send_buffer);
99 var server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface);
104 var conn_reader = stream.reader(io, &recv_buffer);
105 var conn_writer = stream.writer(io, &send_buffer);
106 var server = std.http.Server.init(&conn_reader.interface, &conn_writer.interface);
100107 while (server.reader.state == .ready) {
101108 var request = server.receiveHead() catch |err| switch (err) {
102109 error.HttpConnectionClosing => return,
103110 else => {
104 std.log.err("closing http connection: {s}", .{@errorName(err)});
111 std.log.err("closing http connection: {t}", .{err});
105112 return;
106113 },
107114 };
108115 serveRequest(&request, context) catch |err| switch (err) {
109116 error.WriteFailed => {
110117 if (conn_writer.err) |e| {
111 std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(e) });
118 std.log.err("unable to serve {s}: {t}", .{ request.head.target, e });
112119 } else {
113 std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) });
120 std.log.err("unable to serve {s}: {t}", .{ request.head.target, err });
114121 }
115122 return;
116123 },
117124 else => {
118 std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) });
125 std.log.err("unable to serve {s}: {t}", .{ request.head.target, err });
119126 return;
120127 },
121128 };
......@@ -124,7 +131,8 @@ fn accept(context: *Context, connection: std.net.Server.Connection) void {
124131
125132const Context = struct {
126133 gpa: Allocator,
127 lib_dir: std.fs.Dir,
134 io: Io,
135 lib_dir: Io.Dir,
128136 zig_lib_directory: []const u8,
129137 zig_exe_path: []const u8,
130138 global_cache_path: []const u8,
......@@ -170,10 +178,11 @@ fn serveDocsFile(
170178 content_type: []const u8,
171179) !void {
172180 const gpa = context.gpa;
181 const io = context.io;
173182 // The desired API is actually sendfile, which will require enhancing std.http.Server.
174183 // We load the file with every request so that the user can make changes to the file
175184 // and refresh the HTML page without restarting this server.
176 const file_contents = try context.lib_dir.readFileAlloc(name, gpa, .limited(10 * 1024 * 1024));
185 const file_contents = try context.lib_dir.readFileAlloc(io, name, gpa, .limited(10 * 1024 * 1024));
177186 defer gpa.free(file_contents);
178187 try request.respond(file_contents, .{
179188 .extra_headers = &.{
......@@ -185,6 +194,7 @@ fn serveDocsFile(
185194
186195fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
187196 const gpa = context.gpa;
197 const io = context.io;
188198
189199 var send_buffer: [0x4000]u8 = undefined;
190200 var response = try request.respondStreaming(&send_buffer, .{
......@@ -196,8 +206,8 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
196206 },
197207 });
198208
199 var std_dir = try context.lib_dir.openDir("std", .{ .iterate = true });
200 defer std_dir.close();
209 var std_dir = try context.lib_dir.openDir(io, "std", .{ .iterate = true });
210 defer std_dir.close(io);
201211
202212 var walker = try std_dir.walk(gpa);
203213 defer walker.deinit();
......@@ -205,7 +215,7 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
205215 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
206216 archiver.prefix = "std";
207217
208 while (try walker.next()) |entry| {
218 while (try walker.next(io)) |entry| {
209219 switch (entry.kind) {
210220 .file => {
211221 if (!std.mem.endsWith(u8, entry.basename, ".zig"))
......@@ -215,15 +225,16 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
215225 },
216226 else => continue,
217227 }
218 var file = try entry.dir.openFile(entry.basename, .{});
219 defer file.close();
220 const stat = try file.stat();
221 var file_reader: std.fs.File.Reader = .{
228 var file = try entry.dir.openFile(io, entry.basename, .{});
229 defer file.close(io);
230 const stat = try file.stat(io);
231 var file_reader: Io.File.Reader = .{
232 .io = io,
222233 .file = file,
223 .interface = std.fs.File.Reader.initInterface(&.{}),
234 .interface = Io.File.Reader.initInterface(&.{}),
224235 .size = stat.size,
225236 };
226 try archiver.writeFile(entry.path, &file_reader, stat.mtime);
237 try archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime);
227238 }
228239
229240 {
......@@ -245,6 +256,7 @@ fn serveWasm(
245256 optimize_mode: std.builtin.OptimizeMode,
246257) !void {
247258 const gpa = context.gpa;
259 const io = context.io;
248260
249261 var arena_instance = std.heap.ArenaAllocator.init(gpa);
250262 defer arena_instance.deinit();
......@@ -255,7 +267,7 @@ fn serveWasm(
255267 const wasm_base_path = try buildWasmBinary(arena, context, optimize_mode);
256268 const bin_name = try std.zig.binNameAlloc(arena, .{
257269 .root_name = autodoc_root_name,
258 .target = &(std.zig.system.resolveTargetQuery(std.Build.parseTargetQuery(.{
270 .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
259271 .arch_os_abi = autodoc_arch_os_abi,
260272 .cpu_features = autodoc_cpu_features,
261273 }) catch unreachable) catch unreachable),
......@@ -263,7 +275,7 @@ fn serveWasm(
263275 });
264276 // std.http.Server does not have a sendfile API yet.
265277 const bin_path = try wasm_base_path.join(arena, bin_name);
266 const file_contents = try bin_path.root_dir.handle.readFileAlloc(bin_path.sub_path, gpa, .limited(10 * 1024 * 1024));
278 const file_contents = try bin_path.root_dir.handle.readFileAlloc(io, bin_path.sub_path, gpa, .limited(10 * 1024 * 1024));
267279 defer gpa.free(file_contents);
268280 try request.respond(file_contents, .{
269281 .extra_headers = &.{
......@@ -283,6 +295,7 @@ fn buildWasmBinary(
283295 optimize_mode: std.builtin.OptimizeMode,
284296) !Cache.Path {
285297 const gpa = context.gpa;
298 const io = context.io;
286299
287300 var argv: std.ArrayList([]const u8) = .empty;
288301
......@@ -315,16 +328,16 @@ fn buildWasmBinary(
315328 child.stdin_behavior = .Pipe;
316329 child.stdout_behavior = .Pipe;
317330 child.stderr_behavior = .Pipe;
318 try child.spawn();
331 try child.spawn(io);
319332
320 var poller = std.Io.poll(gpa, enum { stdout, stderr }, .{
333 var poller = Io.poll(gpa, enum { stdout, stderr }, .{
321334 .stdout = child.stdout.?,
322335 .stderr = child.stderr.?,
323336 });
324337 defer poller.deinit();
325338
326 try sendMessage(child.stdin.?, .update);
327 try sendMessage(child.stdin.?, .exit);
339 try sendMessage(io, child.stdin.?, .update);
340 try sendMessage(io, child.stdin.?, .exit);
328341
329342 var result: ?Cache.Path = null;
330343 var result_error_bundle = std.zig.ErrorBundle.empty;
......@@ -348,7 +361,7 @@ fn buildWasmBinary(
348361 result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
349362 },
350363 .emit_digest => {
351 var r: std.Io.Reader = .fixed(body);
364 var r: Io.Reader = .fixed(body);
352365 const emit_digest = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
353366 if (!emit_digest.flags.cache_hit) {
354367 std.log.info("source changes detected; rebuilt wasm component", .{});
......@@ -371,10 +384,10 @@ fn buildWasmBinary(
371384 }
372385
373386 // Send EOF to stdin.
374 child.stdin.?.close();
387 child.stdin.?.close(io);
375388 child.stdin = null;
376389
377 switch (try child.wait()) {
390 switch (try child.wait(io)) {
378391 .Exited => |code| {
379392 if (code != 0) {
380393 std.log.err(
......@@ -394,7 +407,7 @@ fn buildWasmBinary(
394407 }
395408
396409 if (result_error_bundle.errorMessageCount() > 0) {
397 result_error_bundle.renderToStdErr(.{}, true);
410 try result_error_bundle.renderToStderr(io, .{}, .auto);
398411 std.log.err("the following command failed with {d} compilation errors:\n{s}", .{
399412 result_error_bundle.errorMessageCount(),
400413 try std.Build.Step.allocPrintCmd(arena, null, argv.items),
......@@ -410,24 +423,24 @@ fn buildWasmBinary(
410423 };
411424}
412425
413fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
426fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
414427 const header: std.zig.Client.Message.Header = .{
415428 .tag = tag,
416429 .bytes_len = 0,
417430 };
418 var w = file.writer(&.{});
431 var w = file.writer(io, &.{});
419432 w.interface.writeStruct(header, .little) catch |err| switch (err) {
420433 error.WriteFailed => return w.err.?,
421434 };
422435}
423436
424fn openBrowserTab(gpa: Allocator, url: []const u8) !void {
437fn openBrowserTab(gpa: Allocator, io: Io, url: []const u8) !void {
425438 // Until https://github.com/ziglang/zig/issues/19205 is implemented, we
426439 // spawn a thread for this child process.
427 _ = try std.Thread.spawn(.{}, openBrowserTabThread, .{ gpa, url });
440 _ = try std.Thread.spawn(.{}, openBrowserTabThread, .{ gpa, io, url });
428441}
429442
430fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void {
443fn openBrowserTabThread(gpa: Allocator, io: Io, url: []const u8) !void {
431444 const main_exe = switch (builtin.os.tag) {
432445 .windows => "explorer",
433446 .macos => "open",
......@@ -437,6 +450,6 @@ fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void {
437450 child.stdin_behavior = .Ignore;
438451 child.stdout_behavior = .Ignore;
439452 child.stderr_behavior = .Ignore;
440 try child.spawn();
441 _ = try child.wait();
453 try child.spawn(io);
454 _ = try child.wait(io);
442455}
lib/compiler/test_runner.zig+26-20
......@@ -17,7 +17,7 @@ var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);
1717var fba_buffer: [8192]u8 = undefined;
1818var stdin_buffer: [4096]u8 = undefined;
1919var stdout_buffer: [4096]u8 = undefined;
20var runner_threaded_io: Io.Threaded = .init_single_threaded;
20const runner_threaded_io: Io = Io.Threaded.global_single_threaded.ioBasic();
2121
2222/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether
2323/// the test runner will communicate with the build runner via `std.zig.Server`.
......@@ -74,8 +74,8 @@ pub fn main() void {
7474
7575fn mainServer() !void {
7676 @disableInstrumentation();
77 var stdin_reader = std.fs.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer);
78 var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
77 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);
78 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);
7979 var server = try std.zig.Server.init(.{
8080 .in = &stdin_reader.interface,
8181 .out = &stdout_writer.interface,
......@@ -131,7 +131,7 @@ fn mainServer() !void {
131131
132132 .run_test => {
133133 testing.allocator_instance = .{};
134 testing.io_instance = .init(testing.allocator);
134 testing.io_instance = .init(testing.allocator, .{});
135135 log_err_count = 0;
136136 const index = try server.receiveBody_u32();
137137 const test_fn = builtin.test_functions[index];
......@@ -224,16 +224,16 @@ fn mainTerminal() void {
224224 var skip_count: usize = 0;
225225 var fail_count: usize = 0;
226226 var fuzz_count: usize = 0;
227 const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(.{
227 const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(runner_threaded_io, .{
228228 .root_name = "Test",
229229 .estimated_total_items = test_fn_list.len,
230230 });
231 const have_tty = std.fs.File.stderr().isTty();
231 const have_tty = Io.File.stderr().isTty(runner_threaded_io) catch unreachable;
232232
233233 var leaks: usize = 0;
234234 for (test_fn_list, 0..) |test_fn, i| {
235235 testing.allocator_instance = .{};
236 testing.io_instance = .init(testing.allocator);
236 testing.io_instance = .init(testing.allocator, .{});
237237 defer {
238238 testing.io_instance.deinit();
239239 if (testing.allocator_instance.deinit() == .leak) leaks += 1;
......@@ -318,7 +318,7 @@ pub fn log(
318318/// work-in-progress backends can handle it.
319319pub fn mainSimple() anyerror!void {
320320 @disableInstrumentation();
321 // is the backend capable of calling `std.fs.File.writeAll`?
321 // is the backend capable of calling `Io.File.writeAll`?
322322 const enable_write = switch (builtin.zig_backend) {
323323 .stage2_aarch64, .stage2_riscv64 => true,
324324 else => false,
......@@ -329,35 +329,37 @@ pub fn mainSimple() anyerror!void {
329329 else => false,
330330 };
331331
332 testing.io_instance = .init(testing.allocator, .{});
333
332334 var passed: u64 = 0;
333335 var skipped: u64 = 0;
334336 var failed: u64 = 0;
335337
336338 // we don't want to bring in File and Writer if the backend doesn't support it
337 const stdout = if (enable_write) std.fs.File.stdout() else {};
339 const stdout = if (enable_write) Io.File.stdout() else {};
338340
339341 for (builtin.test_functions) |test_fn| {
340342 if (enable_write) {
341 stdout.writeAll(test_fn.name) catch {};
342 stdout.writeAll("... ") catch {};
343 stdout.writeStreamingAll(runner_threaded_io, test_fn.name) catch {};
344 stdout.writeStreamingAll(runner_threaded_io, "... ") catch {};
343345 }
344346 if (test_fn.func()) |_| {
345 if (enable_write) stdout.writeAll("PASS\n") catch {};
347 if (enable_write) stdout.writeStreamingAll(runner_threaded_io, "PASS\n") catch {};
346348 } else |err| {
347349 if (err != error.SkipZigTest) {
348 if (enable_write) stdout.writeAll("FAIL\n") catch {};
350 if (enable_write) stdout.writeStreamingAll(runner_threaded_io, "FAIL\n") catch {};
349351 failed += 1;
350352 if (!enable_write) return err;
351353 continue;
352354 }
353 if (enable_write) stdout.writeAll("SKIP\n") catch {};
355 if (enable_write) stdout.writeStreamingAll(runner_threaded_io, "SKIP\n") catch {};
354356 skipped += 1;
355357 continue;
356358 }
357359 passed += 1;
358360 }
359361 if (enable_print) {
360 var stdout_writer = stdout.writer(&.{});
362 var stdout_writer = stdout.writer(runner_threaded_io, &.{});
361363 stdout_writer.interface.print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {};
362364 }
363365 if (failed != 0) std.process.exit(1);
......@@ -405,15 +407,19 @@ pub fn fuzz(
405407 testOne(ctx, input.toSlice()) catch |err| switch (err) {
406408 error.SkipZigTest => return,
407409 else => {
408 std.debug.lockStdErr();
409 if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace);
410 std.debug.print("failed with error.{t}\n", .{err});
410 const stderr = std.debug.lockStderr(&.{}, null).terminal();
411 p: {
412 if (@errorReturnTrace()) |trace| {
413 std.debug.writeStackTrace(trace, stderr) catch break :p;
414 }
415 stderr.writer.print("failed with error.{t}\n", .{err}) catch break :p;
416 }
411417 std.process.exit(1);
412418 },
413419 };
414420 if (log_err_count != 0) {
415 std.debug.lockStdErr();
416 std.debug.print("error logs detected\n", .{});
421 const stderr = std.debug.lockStderr(&.{}, .no_color);
422 stderr.interface.print("error logs detected\n", .{}) catch {};
417423 std.process.exit(1);
418424 }
419425 }
lib/compiler/translate-c/main.zig+23-18
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const assert = std.debug.assert;
34const mem = std.mem;
45const process = std.process;
......@@ -18,7 +19,7 @@ pub fn main() u8 {
1819 defer arena_instance.deinit();
1920 const arena = arena_instance.allocator();
2021
21 var threaded: std.Io.Threaded = .init(gpa);
22 var threaded: std.Io.Threaded = .init(gpa, .{});
2223 defer threaded.deinit();
2324 const io = threaded.io();
2425
......@@ -33,11 +34,14 @@ pub fn main() u8 {
3334 zig_integration = true;
3435 }
3536
37 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet();
38 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet();
39
3640 var stderr_buf: [1024]u8 = undefined;
37 var stderr = std.fs.File.stderr().writer(&stderr_buf);
41 var stderr = Io.File.stderr().writer(io, &stderr_buf);
3842 var diagnostics: aro.Diagnostics = switch (zig_integration) {
3943 false => .{ .output = .{ .to_writer = .{
40 .color = .detect(stderr.file),
44 .mode = Io.Terminal.Mode.detect(io, stderr.file, NO_COLOR, CLICOLOR_FORCE) catch unreachable,
4145 .writer = &stderr.interface,
4246 } } },
4347 true => .{ .output = .{ .to_list = .{
......@@ -46,7 +50,7 @@ pub fn main() u8 {
4650 };
4751 defer diagnostics.deinit();
4852
49 var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, std.fs.cwd()) catch |err| switch (err) {
53 var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, Io.Dir.cwd()) catch |err| switch (err) {
5054 error.OutOfMemory => {
5155 std.debug.print("ran out of memory initializing C compilation\n", .{});
5256 if (fast_exit) process.exit(1);
......@@ -68,7 +72,7 @@ pub fn main() u8 {
6872 return 1;
6973 },
7074 error.FatalError => if (zig_integration) {
71 serveErrorBundle(arena, &diagnostics) catch |bundle_err| {
75 serveErrorBundle(arena, io, &diagnostics) catch |bundle_err| {
7276 std.debug.print("unable to serve error bundle: {}\n", .{bundle_err});
7377 if (fast_exit) process.exit(1);
7478 return 1;
......@@ -92,14 +96,14 @@ pub fn main() u8 {
9296 return @intFromBool(comp.diagnostics.errors != 0);
9397}
9498
95fn serveErrorBundle(arena: std.mem.Allocator, diagnostics: *const aro.Diagnostics) !void {
99fn serveErrorBundle(arena: std.mem.Allocator, io: Io, diagnostics: *const aro.Diagnostics) !void {
96100 const error_bundle = try compiler_util.aroDiagnosticsToErrorBundle(
97101 diagnostics,
98102 arena,
99103 "translation failure",
100104 );
101105 var stdout_buffer: [1024]u8 = undefined;
102 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
106 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
103107 var server: std.zig.Server = .{
104108 .out = &stdout_writer.interface,
105109 .in = undefined,
......@@ -121,6 +125,7 @@ pub const usage =
121125
122126fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration: bool) !void {
123127 const gpa = d.comp.gpa;
128 const io = d.comp.io;
124129
125130 const aro_args = args: {
126131 var i: usize = 0;
......@@ -128,13 +133,13 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration
128133 args[i] = arg;
129134 if (mem.eql(u8, arg, "--help")) {
130135 var stdout_buf: [512]u8 = undefined;
131 var stdout = std.fs.File.stdout().writer(&stdout_buf);
136 var stdout = Io.File.stdout().writer(io, &stdout_buf);
132137 try stdout.interface.print(usage, .{args[0]});
133138 try stdout.interface.flush();
134139 return;
135140 } else if (mem.eql(u8, arg, "--version")) {
136141 var stdout_buf: [512]u8 = undefined;
137 var stdout = std.fs.File.stdout().writer(&stdout_buf);
142 var stdout = Io.File.stdout().writer(io, &stdout_buf);
138143 // TODO add version
139144 try stdout.interface.writeAll("0.0.0-dev\n");
140145 try stdout.interface.flush();
......@@ -224,13 +229,13 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration
224229 const dep_file_name = try d.getDepFileName(source, out_buf[0..std.fs.max_name_bytes]);
225230
226231 const file = if (dep_file_name) |path|
227 d.comp.cwd.createFile(path, .{}) catch |er|
232 d.comp.cwd.createFile(io, path, .{}) catch |er|
228233 return d.fatal("unable to create dependency file '{s}': {s}", .{ path, aro.Driver.errorDescription(er) })
229234 else
230 std.fs.File.stdout();
231 defer if (dep_file_name != null) file.close();
235 Io.File.stdout();
236 defer if (dep_file_name != null) file.close(io);
232237
233 var file_writer = file.writer(&out_buf);
238 var file_writer = file.writer(io, &out_buf);
234239 dep_file.write(&file_writer.interface) catch
235240 return d.fatal("unable to write dependency file: {s}", .{aro.Driver.errorDescription(file_writer.err.?)});
236241 }
......@@ -245,23 +250,23 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration
245250
246251 var close_out_file = false;
247252 var out_file_path: []const u8 = "<stdout>";
248 var out_file: std.fs.File = .stdout();
249 defer if (close_out_file) out_file.close();
253 var out_file: Io.File = .stdout();
254 defer if (close_out_file) out_file.close(io);
250255
251256 if (d.output_name) |path| blk: {
252257 if (std.mem.eql(u8, path, "-")) break :blk;
253258 if (std.fs.path.dirname(path)) |dirname| {
254 std.fs.cwd().makePath(dirname) catch |err|
259 Io.Dir.cwd().createDirPath(io, dirname) catch |err|
255260 return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) });
256261 }
257 out_file = std.fs.cwd().createFile(path, .{}) catch |err| {
262 out_file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| {
258263 return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) });
259264 };
260265 close_out_file = true;
261266 out_file_path = path;
262267 }
263268
264 var out_writer = out_file.writer(&out_buf);
269 var out_writer = out_file.writer(io, &out_buf);
265270 out_writer.interface.writeAll(rendered_zig) catch {};
266271 out_writer.interface.flush() catch {};
267272 if (out_writer.err) |write_err|
lib/compiler_rt/emutls.zig+1-1
......@@ -7,7 +7,7 @@ const std = @import("std");
77const builtin = @import("builtin");
88const common = @import("common.zig");
99
10const abort = std.posix.abort;
10const abort = std.process.abort;
1111const assert = std.debug.assert;
1212const expect = std.testing.expect;
1313
lib/fuzzer.zig+39-45
......@@ -1,18 +1,22 @@
11const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
24const std = @import("std");
5const Io = std.Io;
36const fatal = std.process.fatal;
47const mem = std.mem;
58const math = std.math;
6const Allocator = mem.Allocator;
9const Allocator = std.mem.Allocator;
710const assert = std.debug.assert;
811const panic = std.debug.panic;
912const abi = std.Build.abi.fuzz;
10const native_endian = builtin.cpu.arch.endian();
1113
1214pub const std_options = std.Options{
1315 .logFn = logOverride,
1416};
1517
18const io = std.Io.Threaded.global_single_threaded.ioBasic();
19
1620fn logOverride(
1721 comptime level: std.log.Level,
1822 comptime scope: @EnumLiteral(),
......@@ -21,12 +25,12 @@ fn logOverride(
2125) void {
2226 const f = log_f orelse
2327 panic("attempt to use log before initialization, message:\n" ++ format, args);
24 f.lock(.exclusive) catch |e| panic("failed to lock logging file: {t}", .{e});
25 defer f.unlock();
28 f.lock(io, .exclusive) catch |e| panic("failed to lock logging file: {t}", .{e});
29 defer f.unlock(io);
2630
2731 var buf: [256]u8 = undefined;
28 var fw = f.writer(&buf);
29 const end = f.getEndPos() catch |e| panic("failed to get fuzzer log file end: {t}", .{e});
32 var fw = f.writer(io, &buf);
33 const end = f.length(io) catch |e| panic("failed to get fuzzer log file end: {t}", .{e});
3034 fw.seekTo(end) catch |e| panic("failed to seek to fuzzer log file end: {t}", .{e});
3135
3236 const prefix1 = comptime level.asText();
......@@ -45,7 +49,7 @@ const gpa = switch (builtin.mode) {
4549};
4650
4751/// Part of `exec`, however seperate to allow it to be set before `exec` is.
48var log_f: ?std.fs.File = null;
52var log_f: ?Io.File = null;
4953var exec: Executable = .preinit;
5054var inst: Instrumentation = .preinit;
5155var fuzzer: Fuzzer = undefined;
......@@ -59,7 +63,7 @@ const Executable = struct {
5963 /// Tracks the hit count for each pc as updated by the process's instrumentation.
6064 pc_counters: []u8,
6165
62 cache_f: std.fs.Dir,
66 cache_f: Io.Dir,
6367 /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed
6468 /// while the fuzzer is running.
6569 shared_seen_pcs: MemoryMappedList,
......@@ -76,16 +80,16 @@ const Executable = struct {
7680 .pc_digest = undefined,
7781 };
7882
79 fn getCoverageFile(cache_dir: std.fs.Dir, pcs: []const usize, pc_digest: u64) MemoryMappedList {
83 fn getCoverageFile(cache_dir: Io.Dir, pcs: []const usize, pc_digest: u64) MemoryMappedList {
8084 const pc_bitset_usizes = bitsetUsizes(pcs.len);
8185 const coverage_file_name = std.fmt.hex(pc_digest);
8286 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
8387 comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);
8488
85 var v = cache_dir.makeOpenPath("v", .{}) catch |e|
89 var v = cache_dir.createDirPathOpen(io, "v", .{}) catch |e|
8690 panic("failed to create directory 'v': {t}", .{e});
87 defer v.close();
88 const coverage_file, const populate = if (v.createFile(&coverage_file_name, .{
91 defer v.close(io);
92 const coverage_file, const populate = if (v.createFile(io, &coverage_file_name, .{
8993 .read = true,
9094 // If we create the file, we want to block other processes while we populate it
9195 .lock = .exclusive,
......@@ -93,7 +97,7 @@ const Executable = struct {
9397 })) |f|
9498 .{ f, true }
9599 else |e| switch (e) {
96 error.PathAlreadyExists => .{ v.openFile(&coverage_file_name, .{
100 error.PathAlreadyExists => .{ v.openFile(io, &coverage_file_name, .{
97101 .mode = .read_write,
98102 .lock = .shared,
99103 }) catch |e2| panic(
......@@ -108,7 +112,7 @@ const Executable = struct {
108112 pcs.len * @sizeOf(usize);
109113
110114 if (populate) {
111 defer coverage_file.lock(.shared) catch |e| panic(
115 defer coverage_file.lock(io, .shared) catch |e| panic(
112116 "failed to demote lock for coverage file '{s}': {t}",
113117 .{ &coverage_file_name, e },
114118 );
......@@ -130,10 +134,8 @@ const Executable = struct {
130134 }
131135 return map;
132136 } else {
133 const size = coverage_file.getEndPos() catch |e| panic(
134 "failed to stat coverage file '{s}': {t}",
135 .{ &coverage_file_name, e },
136 );
137 const size = coverage_file.length(io) catch |e|
138 panic("failed to stat coverage file '{s}': {t}", .{ &coverage_file_name, e });
137139 if (size != coverage_file_len) panic(
138140 "incompatible existing coverage file '{s}' (differing lengths: {} != {})",
139141 .{ &coverage_file_name, size, coverage_file_len },
......@@ -165,13 +167,11 @@ const Executable = struct {
165167 pub fn init(cache_dir_path: []const u8) Executable {
166168 var self: Executable = undefined;
167169
168 const cache_dir = std.fs.cwd().makeOpenPath(cache_dir_path, .{}) catch |e| panic(
169 "failed to open directory '{s}': {t}",
170 .{ cache_dir_path, e },
171 );
172 log_f = cache_dir.createFile("tmp/libfuzzer.log", .{ .truncate = false }) catch |e|
170 const cache_dir = Io.Dir.cwd().createDirPathOpen(io, cache_dir_path, .{}) catch |e|
171 panic("failed to open directory '{s}': {t}", .{ cache_dir_path, e });
172 log_f = cache_dir.createFile(io, "tmp/libfuzzer.log", .{ .truncate = false }) catch |e|
173173 panic("failed to create file 'tmp/libfuzzer.log': {t}", .{e});
174 self.cache_f = cache_dir.makeOpenPath("f", .{}) catch |e|
174 self.cache_f = cache_dir.createDirPathOpen(io, "f", .{}) catch |e|
175175 panic("failed to open directory 'f': {t}", .{e});
176176
177177 // Linkers are expected to automatically add symbols prefixed with these for the start and
......@@ -391,7 +391,7 @@ const Fuzzer = struct {
391391 mutations: std.ArrayList(Mutation) = .empty,
392392
393393 /// Filesystem directory containing found inputs for future runs
394 corpus_dir: std.fs.Dir,
394 corpus_dir: Io.Dir,
395395 corpus_dir_idx: usize = 0,
396396
397397 pub fn init(test_one: abi.TestOne, unit_test_name: []const u8) Fuzzer {
......@@ -405,10 +405,10 @@ const Fuzzer = struct {
405405 };
406406 const arena = self.arena_ctx.allocator();
407407
408 self.corpus_dir = exec.cache_f.makeOpenPath(unit_test_name, .{}) catch |e|
408 self.corpus_dir = exec.cache_f.createDirPathOpen(io, unit_test_name, .{}) catch |e|
409409 panic("failed to open directory '{s}': {t}", .{ unit_test_name, e });
410410 self.input = in: {
411 const f = self.corpus_dir.createFile("in", .{
411 const f = self.corpus_dir.createFile(io, "in", .{
412412 .read = true,
413413 .truncate = false,
414414 // In case any other fuzz tests are running under the same test name,
......@@ -419,7 +419,7 @@ const Fuzzer = struct {
419419 error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"),
420420 else => panic("failed to create input file 'in': {t}", .{e}),
421421 };
422 const size = f.getEndPos() catch |e| panic("failed to stat input file 'in': {t}", .{e});
422 const size = f.length(io) catch |e| panic("failed to stat input file 'in': {t}", .{e});
423423 const map = (if (size < std.heap.page_size_max)
424424 MemoryMappedList.create(f, 8, std.heap.page_size_max)
425425 else
......@@ -445,6 +445,7 @@ const Fuzzer = struct {
445445 while (true) {
446446 var name_buf: [@sizeOf(usize) * 2]u8 = undefined;
447447 const bytes = self.corpus_dir.readFileAlloc(
448 io,
448449 std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable,
449450 arena,
450451 .unlimited,
......@@ -466,7 +467,7 @@ const Fuzzer = struct {
466467 self.input.deinit();
467468 self.corpus.deinit(gpa);
468469 self.mutations.deinit(gpa);
469 self.corpus_dir.close();
470 self.corpus_dir.close(io);
470471 self.arena_ctx.deinit();
471472 self.* = undefined;
472473 }
......@@ -573,17 +574,10 @@ const Fuzzer = struct {
573574
574575 // Write new corpus to cache
575576 var name_buf: [@sizeOf(usize) * 2]u8 = undefined;
576 self.corpus_dir.writeFile(.{
577 .sub_path = std.fmt.bufPrint(
578 &name_buf,
579 "{x}",
580 .{self.corpus_dir_idx},
581 ) catch unreachable,
577 self.corpus_dir.writeFile(io, .{
578 .sub_path = std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable,
582579 .data = bytes,
583 }) catch |e| panic(
584 "failed to write corpus file '{x}': {t}",
585 .{ self.corpus_dir_idx, e },
586 );
580 }) catch |e| panic("failed to write corpus file '{x}': {t}", .{ self.corpus_dir_idx, e });
587581 self.corpus_dir_idx += 1;
588582 }
589583 }
......@@ -1320,9 +1314,9 @@ pub const MemoryMappedList = struct {
13201314 /// How many bytes this list can hold without allocating additional memory.
13211315 capacity: usize,
13221316 /// The file is kept open so that it can be resized.
1323 file: std.fs.File,
1317 file: Io.File,
13241318
1325 pub fn init(file: std.fs.File, length: usize, capacity: usize) !MemoryMappedList {
1319 pub fn init(file: Io.File, length: usize, capacity: usize) !MemoryMappedList {
13261320 const ptr = try std.posix.mmap(
13271321 null,
13281322 capacity,
......@@ -1338,13 +1332,13 @@ pub const MemoryMappedList = struct {
13381332 };
13391333 }
13401334
1341 pub fn create(file: std.fs.File, length: usize, capacity: usize) !MemoryMappedList {
1342 try file.setEndPos(capacity);
1335 pub fn create(file: Io.File, length: usize, capacity: usize) !MemoryMappedList {
1336 try file.setLength(io, capacity);
13431337 return init(file, length, capacity);
13441338 }
13451339
13461340 pub fn deinit(l: *MemoryMappedList) void {
1347 l.file.close();
1341 l.file.close(io);
13481342 std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));
13491343 l.* = undefined;
13501344 }
......@@ -1369,7 +1363,7 @@ pub const MemoryMappedList = struct {
13691363 if (l.capacity >= new_capacity) return;
13701364
13711365 std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));
1372 try l.file.setEndPos(new_capacity);
1366 try l.file.setLength(io, new_capacity);
13731367 l.* = try init(l.file, l.items.len, new_capacity);
13741368 }
13751369
lib/init/src/main.zig+24-2
......@@ -1,10 +1,32 @@
11const std = @import("std");
2const Io = std.Io;
3
24const _NAME = @import(".NAME");
35
46pub fn main() !void {
5 // Prints to stderr, ignoring potential errors.
7 // Prints to stderr, unbuffered, ignoring potential errors.
68 std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
7 try _NAME.bufferedPrint();
9
10 // In order to allocate memory we must construct an `Allocator` instance.
11 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
12 defer _ = debug_allocator.deinit(); // This checks for leaks.
13 const gpa = debug_allocator.allocator();
14
15 // In order to do I/O operations we must construct an `Io` instance.
16 var threaded: std.Io.Threaded = .init(gpa, .{});
17 defer threaded.deinit();
18 const io = threaded.io();
19
20 // Stdout is for the actual output of your application, for example if you
21 // are implementing gzip, then only the compressed bytes should be sent to
22 // stdout, not any debugging messages.
23 var stdout_buffer: [1024]u8 = undefined;
24 var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer);
25 const stdout_writer = &stdout_file_writer.interface;
26
27 try _NAME.printAnotherMessage(stdout_writer);
28
29 try stdout_writer.flush(); // Don't forget to flush!
830}
931
1032test "simple test" {
lib/init/src/root.zig+7-12
......@@ -1,17 +1,12 @@
1//! By convention, root.zig is the root source file when making a library.
1//! By convention, root.zig is the root source file when making a package.
22const std = @import("std");
3const Io = std.Io;
34
4pub fn bufferedPrint() !void {
5 // Stdout is for the actual output of your application, for example if you
6 // are implementing gzip, then only the compressed bytes should be sent to
7 // stdout, not any debugging messages.
8 var stdout_buffer: [1024]u8 = undefined;
9 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
10 const stdout = &stdout_writer.interface;
11
12 try stdout.print("Run `zig build test` to run the tests.\n", .{});
13
14 try stdout.flush(); // Don't forget to flush!
5/// This is a documentation comment to explain the `printAnotherMessage` function below.
6///
7/// Accepting an `Io.Writer` instance is a handy way to write reusable code.
8pub fn printAnotherMessage(writer: *Io.Writer) Io.Writer.Error!void {
9 try writer.print("Run `zig build test` to run the tests.\n", .{});
1510}
1611
1712pub fn add(a: i32, b: i32) i32 {
lib/std/Build.zig+83-61
......@@ -1,21 +1,20 @@
1const Build = @This();
12const builtin = @import("builtin");
23
34const std = @import("std.zig");
45const Io = std.Io;
56const fs = std.fs;
67const mem = std.mem;
7const debug = std.debug;
88const panic = std.debug.panic;
9const assert = debug.assert;
9const assert = std.debug.assert;
1010const log = std.log;
1111const StringHashMap = std.StringHashMap;
12const Allocator = mem.Allocator;
12const Allocator = std.mem.Allocator;
1313const Target = std.Target;
1414const process = std.process;
1515const EnvMap = std.process.EnvMap;
16const File = fs.File;
16const File = std.Io.File;
1717const Sha256 = std.crypto.hash.sha2.Sha256;
18const Build = @This();
1918const ArrayList = std.ArrayList;
2019
2120pub const Cache = @import("Build/Cache.zig");
......@@ -130,6 +129,9 @@ pub const Graph = struct {
130129 dependency_cache: InitializedDepMap = .empty,
131130 allow_so_scripts: ?bool = null,
132131 time_report: bool,
132 /// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also
133 /// respects the '--color' flag.
134 stderr_mode: ?Io.Terminal.Mode = null,
133135};
134136
135137const AvailableDeps = []const struct { []const u8, []const u8 };
......@@ -1699,21 +1701,20 @@ pub fn addCheckFile(
16991701 return Step.CheckFile.create(b, file_source, options);
17001702}
17011703
1702pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Dir.StatFileError)!void {
1703 if (b.verbose) {
1704 log.info("truncate {s}", .{dest_path});
1705 }
1706 const cwd = fs.cwd();
1707 var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) {
1704pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.CreateDirError || Io.Dir.StatFileError)!void {
1705 const io = b.graph.io;
1706 if (b.verbose) log.info("truncate {s}", .{dest_path});
1707 const cwd = Io.Dir.cwd();
1708 var src_file = cwd.createFile(io, dest_path, .{}) catch |err| switch (err) {
17081709 error.FileNotFound => blk: {
17091710 if (fs.path.dirname(dest_path)) |dirname| {
1710 try cwd.makePath(dirname);
1711 try cwd.createDirPath(io, dirname);
17111712 }
1712 break :blk try cwd.createFile(dest_path, .{});
1713 break :blk try cwd.createFile(io, dest_path, .{});
17131714 },
17141715 else => |e| return e,
17151716 };
1716 src_file.close();
1717 src_file.close(io);
17171718}
17181719
17191720/// References a file or directory relative to the source root.
......@@ -1761,7 +1762,10 @@ fn supportedWindowsProgramExtension(ext: []const u8) bool {
17611762}
17621763
17631764fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {
1764 if (fs.realpathAlloc(b.allocator, full_path)) |p| {
1765 const io = b.graph.io;
1766 const arena = b.allocator;
1767
1768 if (Io.Dir.realPathFileAbsoluteAlloc(io, full_path, arena)) |p| {
17651769 return p;
17661770 } else |err| switch (err) {
17671771 error.OutOfMemory => @panic("OOM"),
......@@ -1775,7 +1779,11 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {
17751779 while (it.next()) |ext| {
17761780 if (!supportedWindowsProgramExtension(ext)) continue;
17771781
1778 return fs.realpathAlloc(b.allocator, b.fmt("{s}{s}", .{ full_path, ext })) catch |err| switch (err) {
1782 return Io.Dir.realPathFileAbsoluteAlloc(
1783 io,
1784 b.fmt("{s}{s}", .{ full_path, ext }),
1785 arena,
1786 ) catch |err| switch (err) {
17791787 error.OutOfMemory => @panic("OOM"),
17801788 else => continue,
17811789 };
......@@ -1839,7 +1847,7 @@ pub fn runAllowFail(
18391847 child.env_map = &b.graph.env_map;
18401848
18411849 try Step.handleVerbose2(b, null, child.env_map, argv);
1842 try child.spawn();
1850 try child.spawn(io);
18431851
18441852 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
18451853 const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {
......@@ -1847,7 +1855,7 @@ pub fn runAllowFail(
18471855 };
18481856 errdefer b.allocator.free(stdout);
18491857
1850 const term = try child.wait();
1858 const term = try child.wait(io);
18511859 switch (term) {
18521860 .Exited => |code| {
18531861 if (code != 0) {
......@@ -2091,7 +2099,7 @@ pub fn dependencyFromBuildZig(
20912099 }
20922100
20932101 const full_path = b.pathFromRoot("build.zig.zon");
2094 debug.panic("'{}' is not a build.zig struct of a dependency in '{s}'", .{ build_zig, full_path });
2102 std.debug.panic("'{}' is not a build.zig struct of a dependency in '{s}'", .{ build_zig, full_path });
20952103}
20962104
20972105fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {
......@@ -2185,6 +2193,7 @@ fn dependencyInner(
21852193 pkg_deps: AvailableDeps,
21862194 args: anytype,
21872195) *Dependency {
2196 const io = b.graph.io;
21882197 const user_input_options = userInputOptionsFromArgs(b.allocator, args);
21892198 if (b.graph.dependency_cache.getContext(.{
21902199 .build_root_string = build_root_string,
......@@ -2194,7 +2203,7 @@ fn dependencyInner(
21942203
21952204 const build_root: std.Build.Cache.Directory = .{
21962205 .path = build_root_string,
2197 .handle = fs.cwd().openDir(build_root_string, .{}) catch |err| {
2206 .handle = Io.Dir.cwd().openDir(io, build_root_string, .{}) catch |err| {
21982207 std.debug.print("unable to open '{s}': {s}\n", .{
21992208 build_root_string, @errorName(err),
22002209 });
......@@ -2239,7 +2248,7 @@ pub const GeneratedFile = struct {
22392248 /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards.
22402249 path: ?[]const u8 = null,
22412250
2242 /// Deprecated, see `getPath2`.
2251 /// Deprecated, see `getPath3`.
22432252 pub fn getPath(gen: GeneratedFile) []const u8 {
22442253 return gen.step.owner.pathFromCwd(gen.path orelse std.debug.panic(
22452254 "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?",
......@@ -2247,11 +2256,19 @@ pub const GeneratedFile = struct {
22472256 ));
22482257 }
22492258
2259 /// Deprecated, see `getPath3`.
22502260 pub fn getPath2(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) []const u8 {
2261 return getPath3(gen, src_builder, asking_step) catch |err| switch (err) {
2262 error.Canceled => std.process.exit(1),
2263 };
2264 }
2265
2266 pub fn getPath3(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) Io.Cancelable![]const u8 {
22512267 return gen.path orelse {
2252 const w, const ttyconf = debug.lockStderrWriter(&.{});
2253 dumpBadGetPathHelp(gen.step, w, ttyconf, src_builder, asking_step) catch {};
2254 debug.unlockStderrWriter();
2268 const graph = gen.step.owner.graph;
2269 const io = graph.io;
2270 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2271 dumpBadGetPathHelp(gen.step, stderr.terminal(), src_builder, asking_step) catch {};
22552272 @panic("misconfigured build script");
22562273 };
22572274 }
......@@ -2426,22 +2443,29 @@ pub const LazyPath = union(enum) {
24262443 }
24272444 }
24282445
2429 /// Deprecated, see `getPath3`.
2446 /// Deprecated, see `getPath4`.
24302447 pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 {
24312448 return getPath2(lazy_path, src_builder, null);
24322449 }
24332450
2434 /// Deprecated, see `getPath3`.
2451 /// Deprecated, see `getPath4`.
24352452 pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
24362453 const p = getPath3(lazy_path, src_builder, asking_step);
24372454 return src_builder.pathResolve(&.{ p.root_dir.path orelse ".", p.sub_path });
24382455 }
24392456
2457 /// Deprecated, see `getPath4`.
2458 pub fn getPath3(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Cache.Path {
2459 return getPath4(lazy_path, src_builder, asking_step) catch |err| switch (err) {
2460 error.Canceled => std.process.exit(1),
2461 };
2462 }
2463
24402464 /// Intended to be used during the make phase only.
24412465 ///
24422466 /// `asking_step` is only used for debugging purposes; it's the step being
24432467 /// run that is asking for the path.
2444 pub fn getPath3(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Cache.Path {
2468 pub fn getPath4(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Io.Cancelable!Cache.Path {
24452469 switch (lazy_path) {
24462470 .src_path => |sp| return .{
24472471 .root_dir = sp.owner.build_root,
......@@ -2455,12 +2479,15 @@ pub const LazyPath = union(enum) {
24552479 // TODO make gen.file.path not be absolute and use that as the
24562480 // basis for not traversing up too many directories.
24572481
2482 const graph = src_builder.graph;
2483
24582484 var file_path: Cache.Path = .{
24592485 .root_dir = Cache.Directory.cwd(),
24602486 .sub_path = gen.file.path orelse {
2461 const w, const ttyconf = debug.lockStderrWriter(&.{});
2462 dumpBadGetPathHelp(gen.file.step, w, ttyconf, src_builder, asking_step) catch {};
2463 debug.unlockStderrWriter();
2487 const io = graph.io;
2488 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2489 dumpBadGetPathHelp(gen.file.step, stderr.terminal(), src_builder, asking_step) catch {};
2490 io.unlockStderr();
24642491 @panic("misconfigured build script");
24652492 },
24662493 };
......@@ -2550,40 +2577,36 @@ fn dumpBadDirnameHelp(
25502577 comptime msg: []const u8,
25512578 args: anytype,
25522579) anyerror!void {
2553 const w, const tty_config = debug.lockStderrWriter(&.{});
2554 defer debug.unlockStderrWriter();
2580 const stderr = std.debug.lockStderr(&.{}).terminal();
2581 defer std.debug.unlockStderr();
2582 const w = stderr.writer;
25552583
25562584 try w.print(msg, args);
25572585
25582586 if (fail_step) |s| {
2559 tty_config.setColor(w, .red) catch {};
2587 stderr.setColor(.red) catch {};
25602588 try w.writeAll(" The step was created by this stack trace:\n");
2561 tty_config.setColor(w, .reset) catch {};
2589 stderr.setColor(.reset) catch {};
25622590
2563 s.dump(w, tty_config);
2591 s.dump(stderr);
25642592 }
25652593
25662594 if (asking_step) |as| {
2567 tty_config.setColor(w, .red) catch {};
2595 stderr.setColor(.red) catch {};
25682596 try w.print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name});
2569 tty_config.setColor(w, .reset) catch {};
2597 stderr.setColor(.reset) catch {};
25702598
2571 as.dump(w, tty_config);
2599 as.dump(stderr);
25722600 }
25732601
2574 tty_config.setColor(w, .red) catch {};
2575 try w.writeAll(" Hope that helps. Proceeding to panic.\n");
2576 tty_config.setColor(w, .reset) catch {};
2602 stderr.setColor(.red) catch {};
2603 try w.writeAll(" Proceeding to panic.\n");
2604 stderr.setColor(.reset) catch {};
25772605}
25782606
25792607/// In this function the stderr mutex has already been locked.
2580pub fn dumpBadGetPathHelp(
2581 s: *Step,
2582 w: *std.Io.Writer,
2583 tty_config: std.Io.tty.Config,
2584 src_builder: *Build,
2585 asking_step: ?*Step,
2586) anyerror!void {
2608pub fn dumpBadGetPathHelp(s: *Step, t: Io.Terminal, src_builder: *Build, asking_step: ?*Step) anyerror!void {
2609 const w = t.writer;
25872610 try w.print(
25882611 \\getPath() was called on a GeneratedFile that wasn't built yet.
25892612 \\ source package path: {s}
......@@ -2594,21 +2617,21 @@ pub fn dumpBadGetPathHelp(
25942617 s.name,
25952618 });
25962619
2597 tty_config.setColor(w, .red) catch {};
2620 t.setColor(.red) catch {};
25982621 try w.writeAll(" The step was created by this stack trace:\n");
2599 tty_config.setColor(w, .reset) catch {};
2622 t.setColor(.reset) catch {};
26002623
2601 s.dump(w, tty_config);
2624 s.dump(t);
26022625 if (asking_step) |as| {
2603 tty_config.setColor(w, .red) catch {};
2626 t.setColor(.red) catch {};
26042627 try w.print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name});
2605 tty_config.setColor(w, .reset) catch {};
2628 t.setColor(.reset) catch {};
26062629
2607 as.dump(w, tty_config);
2630 as.dump(t);
26082631 }
2609 tty_config.setColor(w, .red) catch {};
2610 try w.writeAll(" Hope that helps. Proceeding to panic.\n");
2611 tty_config.setColor(w, .reset) catch {};
2632 t.setColor(.red) catch {};
2633 try w.writeAll(" Proceeding to panic.\n");
2634 t.setColor(.reset) catch {};
26122635}
26132636
26142637pub const InstallDir = union(enum) {
......@@ -2634,13 +2657,12 @@ pub const InstallDir = union(enum) {
26342657/// source of API breakage in the future, so keep that in mind when using this
26352658/// function.
26362659pub fn makeTempPath(b: *Build) []const u8 {
2660 const io = b.graph.io;
26372661 const rand_int = std.crypto.random.int(u64);
26382662 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
26392663 const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM");
2640 b.cache_root.handle.makePath(tmp_dir_sub_path) catch |err| {
2641 std.debug.print("unable to make tmp path '{s}': {s}\n", .{
2642 result_path, @errorName(err),
2643 });
2664 b.cache_root.handle.createDirPath(io, tmp_dir_sub_path) catch |err| {
2665 std.debug.print("unable to make tmp path '{s}': {t}\n", .{ result_path, err });
26442666 };
26452667 return result_path;
26462668}
lib/std/Build/Cache.zig+103-128
......@@ -8,7 +8,6 @@ const builtin = @import("builtin");
88const std = @import("std");
99const Io = std.Io;
1010const crypto = std.crypto;
11const fs = std.fs;
1211const assert = std.debug.assert;
1312const testing = std.testing;
1413const mem = std.mem;
......@@ -18,7 +17,7 @@ const log = std.log.scoped(.cache);
1817
1918gpa: Allocator,
2019io: Io,
21manifest_dir: fs.Dir,
20manifest_dir: Io.Dir,
2221hash: HashHelper = .{},
2322/// This value is accessed from multiple threads, protected by mutex.
2423recent_problematic_timestamp: Io.Timestamp = .zero,
......@@ -71,7 +70,7 @@ const PrefixedPath = struct {
7170
7271fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {
7372 const gpa = cache.gpa;
74 const resolved_path = try fs.path.resolve(gpa, &.{file_path});
73 const resolved_path = try std.fs.path.resolve(gpa, &.{file_path});
7574 errdefer gpa.free(resolved_path);
7675 return findPrefixResolved(cache, resolved_path);
7776}
......@@ -102,9 +101,9 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
102101}
103102
104103fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
105 const relative = try fs.path.relative(allocator, prefix, path);
104 const relative = try std.fs.path.relative(allocator, prefix, path);
106105 errdefer allocator.free(relative);
107 var component_iterator = fs.path.NativeComponentIterator.init(relative);
106 var component_iterator = std.fs.path.NativeComponentIterator.init(relative);
108107 if (component_iterator.root() != null) {
109108 return error.NotASubPath;
110109 }
......@@ -145,17 +144,17 @@ pub const File = struct {
145144 max_file_size: ?usize,
146145 /// Populated if the user calls `addOpenedFile`.
147146 /// The handle is not owned here.
148 handle: ?fs.File,
147 handle: ?Io.File,
149148 stat: Stat,
150149 bin_digest: BinDigest,
151150 contents: ?[]const u8,
152151
153152 pub const Stat = struct {
154 inode: fs.File.INode,
153 inode: Io.File.INode,
155154 size: u64,
156155 mtime: Io.Timestamp,
157156
158 pub fn fromFs(fs_stat: fs.File.Stat) Stat {
157 pub fn fromFs(fs_stat: Io.File.Stat) Stat {
159158 return .{
160159 .inode = fs_stat.inode,
161160 .size = fs_stat.size,
......@@ -178,7 +177,7 @@ pub const File = struct {
178177 file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new;
179178 }
180179
181 pub fn updateHandle(file: *File, new_handle: ?fs.File) void {
180 pub fn updateHandle(file: *File, new_handle: ?Io.File) void {
182181 const handle = new_handle orelse return;
183182 file.handle = handle;
184183 }
......@@ -293,16 +292,16 @@ pub fn binToHex(bin_digest: BinDigest) HexDigest {
293292}
294293
295294pub const Lock = struct {
296 manifest_file: fs.File,
295 manifest_file: Io.File,
297296
298 pub fn release(lock: *Lock) void {
297 pub fn release(lock: *Lock, io: Io) void {
299298 if (builtin.os.tag == .windows) {
300299 // Windows does not guarantee that locks are immediately unlocked when
301300 // the file handle is closed. See LockFileEx documentation.
302 lock.manifest_file.unlock();
301 lock.manifest_file.unlock(io);
303302 }
304303
305 lock.manifest_file.close();
304 lock.manifest_file.close(io);
306305 lock.* = undefined;
307306 }
308307};
......@@ -311,7 +310,7 @@ pub const Manifest = struct {
311310 cache: *Cache,
312311 /// Current state for incremental hashing.
313312 hash: HashHelper,
314 manifest_file: ?fs.File,
313 manifest_file: ?Io.File,
315314 manifest_dirty: bool,
316315 /// Set this flag to true before calling hit() in order to indicate that
317316 /// upon a cache hit, the code using the cache will not modify the files
......@@ -332,9 +331,9 @@ pub const Manifest = struct {
332331
333332 pub const Diagnostic = union(enum) {
334333 none,
335 manifest_create: fs.File.OpenError,
336 manifest_read: fs.File.ReadError,
337 manifest_lock: fs.File.LockError,
334 manifest_create: Io.File.OpenError,
335 manifest_read: Io.File.Reader.Error,
336 manifest_lock: Io.File.LockError,
338337 file_open: FileOp,
339338 file_stat: FileOp,
340339 file_read: FileOp,
......@@ -393,10 +392,10 @@ pub const Manifest = struct {
393392 }
394393
395394 /// Same as `addFilePath` except the file has already been opened.
396 pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?fs.File, max_file_size: ?usize) !usize {
395 pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?Io.File, max_file_size: ?usize) !usize {
397396 const gpa = m.cache.gpa;
398397 try m.files.ensureUnusedCapacity(gpa, 1);
399 const resolved_path = try fs.path.resolve(gpa, &.{
398 const resolved_path = try std.fs.path.resolve(gpa, &.{
400399 path.root_dir.path orelse ".",
401400 path.subPathOrDot(),
402401 });
......@@ -417,7 +416,7 @@ pub const Manifest = struct {
417416 return addFileInner(self, prefixed_path, null, max_file_size);
418417 }
419418
420 fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?fs.File, max_file_size: ?usize) usize {
419 fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?Io.File, max_file_size: ?usize) usize {
421420 const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});
422421 if (gop.found_existing) {
423422 self.cache.gpa.free(prefixed_path.sub_path);
......@@ -460,7 +459,7 @@ pub const Manifest = struct {
460459 }
461460 }
462461
463 pub fn addDepFile(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
462 pub fn addDepFile(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
464463 assert(self.manifest_file == null);
465464 return self.addDepFileMaybePost(dir, dep_file_sub_path);
466465 }
......@@ -503,11 +502,13 @@ pub const Manifest = struct {
503502 @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest);
504503 manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*;
505504
505 const io = self.cache.io;
506
506507 // We'll try to open the cache with an exclusive lock, but if that would block
507508 // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll
508509 // open with a shared lock instead.
509510 while (true) {
510 if (self.cache.manifest_dir.createFile(&manifest_file_path, .{
511 if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{
511512 .read = true,
512513 .truncate = false,
513514 .lock = .exclusive,
......@@ -518,7 +519,7 @@ pub const Manifest = struct {
518519 break;
519520 } else |err| switch (err) {
520521 error.WouldBlock => {
521 self.manifest_file = self.cache.manifest_dir.openFile(&manifest_file_path, .{
522 self.manifest_file = self.cache.manifest_dir.openFile(io, &manifest_file_path, .{
522523 .mode = .read_write,
523524 .lock = .shared,
524525 }) catch |e| {
......@@ -542,7 +543,7 @@ pub const Manifest = struct {
542543 return error.CacheCheckFailed;
543544 }
544545
545 if (self.cache.manifest_dir.createFile(&manifest_file_path, .{
546 if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{
546547 .read = true,
547548 .truncate = false,
548549 .lock = .exclusive,
......@@ -702,7 +703,7 @@ pub const Manifest = struct {
702703 const file_path = iter.rest();
703704
704705 const stat_size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat;
705 const stat_inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat;
706 const stat_inode = fmt.parseInt(Io.File.INode, inode, 10) catch return error.InvalidFormat;
706707 const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat;
707708 const file_bin_digest = b: {
708709 if (digest_str.len != hex_digest_len) return error.InvalidFormat;
......@@ -758,7 +759,7 @@ pub const Manifest = struct {
758759
759760 const pp = cache_hash_file.prefixed_path;
760761 const dir = self.cache.prefixes()[pp.prefix].handle;
761 const this_file = dir.openFile(pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) {
762 const this_file = dir.openFile(io, pp.sub_path, .{ .mode = .read_only }) catch |err| switch (err) {
762763 error.FileNotFound => {
763764 // Every digest before this one has been populated successfully.
764765 return .{ .miss = .{ .file_digests_populated = idx } };
......@@ -772,9 +773,9 @@ pub const Manifest = struct {
772773 return error.CacheCheckFailed;
773774 },
774775 };
775 defer this_file.close();
776 defer this_file.close(io);
776777
777 const actual_stat = this_file.stat() catch |err| {
778 const actual_stat = this_file.stat(io) catch |err| {
778779 self.diagnostic = .{ .file_stat = .{
779780 .file_index = idx,
780781 .err = err,
......@@ -799,7 +800,7 @@ pub const Manifest = struct {
799800 }
800801
801802 var actual_digest: BinDigest = undefined;
802 hashFile(this_file, &actual_digest) catch |err| {
803 hashFile(io, this_file, &actual_digest) catch |err| {
803804 self.diagnostic = .{ .file_read = .{
804805 .file_index = idx,
805806 .err = err,
......@@ -872,17 +873,17 @@ pub const Manifest = struct {
872873 if (man.want_refresh_timestamp) {
873874 man.want_refresh_timestamp = false;
874875
875 var file = man.cache.manifest_dir.createFile("timestamp", .{
876 var file = man.cache.manifest_dir.createFile(io, "timestamp", .{
876877 .read = true,
877878 .truncate = true,
878879 }) catch |err| switch (err) {
879880 error.Canceled => return error.Canceled,
880881 else => return true,
881882 };
882 defer file.close();
883 defer file.close(io);
883884
884885 // Save locally and also save globally (we still hold the global lock).
885 const stat = file.stat() catch |err| switch (err) {
886 const stat = file.stat(io) catch |err| switch (err) {
886887 error.Canceled => return error.Canceled,
887888 else => return true,
888889 };
......@@ -894,19 +895,24 @@ pub const Manifest = struct {
894895 }
895896
896897 fn populateFileHash(self: *Manifest, ch_file: *File) !void {
898 const io = self.cache.io;
899
897900 if (ch_file.handle) |handle| {
898901 return populateFileHashHandle(self, ch_file, handle);
899902 } else {
900903 const pp = ch_file.prefixed_path;
901904 const dir = self.cache.prefixes()[pp.prefix].handle;
902 const handle = try dir.openFile(pp.sub_path, .{});
903 defer handle.close();
905 const handle = try dir.openFile(io, pp.sub_path, .{});
906 defer handle.close(io);
904907 return populateFileHashHandle(self, ch_file, handle);
905908 }
906909 }
907910
908 fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: fs.File) !void {
909 const actual_stat = try handle.stat();
911 fn populateFileHashHandle(self: *Manifest, ch_file: *File, io_file: Io.File) !void {
912 const io = self.cache.io;
913 const gpa = self.cache.gpa;
914
915 const actual_stat = try io_file.stat(io);
910916 ch_file.stat = .{
911917 .size = actual_stat.size,
912918 .mtime = actual_stat.mtime,
......@@ -920,19 +926,17 @@ pub const Manifest = struct {
920926 }
921927
922928 if (ch_file.max_file_size) |max_file_size| {
923 if (ch_file.stat.size > max_file_size) {
924 return error.FileTooBig;
925 }
929 if (ch_file.stat.size > max_file_size) return error.FileTooBig;
926930
927 const contents = try self.cache.gpa.alloc(u8, @as(usize, @intCast(ch_file.stat.size)));
928 errdefer self.cache.gpa.free(contents);
931 // Hash while reading from disk, to keep the contents in the cpu
932 // cache while doing hashing.
933 const contents = try gpa.alloc(u8, @intCast(ch_file.stat.size));
934 errdefer gpa.free(contents);
929935
930 // Hash while reading from disk, to keep the contents in the cpu cache while
931 // doing hashing.
932936 var hasher = hasher_init;
933937 var off: usize = 0;
934938 while (true) {
935 const bytes_read = try handle.pread(contents[off..], off);
939 const bytes_read = try io_file.readPositional(io, &.{contents[off..]}, off);
936940 if (bytes_read == 0) break;
937941 hasher.update(contents[off..][0..bytes_read]);
938942 off += bytes_read;
......@@ -941,7 +945,7 @@ pub const Manifest = struct {
941945
942946 ch_file.contents = contents;
943947 } else {
944 try hashFile(handle, &ch_file.bin_digest);
948 try hashFile(io, io_file, &ch_file.bin_digest);
945949 }
946950
947951 self.hash.hasher.update(&ch_file.bin_digest);
......@@ -1064,14 +1068,15 @@ pub const Manifest = struct {
10641068 self.hash.hasher.update(&new_file.bin_digest);
10651069 }
10661070
1067 pub fn addDepFilePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
1071 pub fn addDepFilePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
10681072 assert(self.manifest_file != null);
10691073 return self.addDepFileMaybePost(dir, dep_file_sub_path);
10701074 }
10711075
1072 fn addDepFileMaybePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
1076 fn addDepFileMaybePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
10731077 const gpa = self.cache.gpa;
1074 const dep_file_contents = try dir.readFileAlloc(dep_file_sub_path, gpa, .limited(manifest_file_size_max));
1078 const io = self.cache.io;
1079 const dep_file_contents = try dir.readFileAlloc(io, dep_file_sub_path, gpa, .limited(manifest_file_size_max));
10751080 defer gpa.free(dep_file_contents);
10761081
10771082 var error_buf: std.ArrayList(u8) = .empty;
......@@ -1130,13 +1135,13 @@ pub const Manifest = struct {
11301135 /// lock from exclusive to shared.
11311136 pub fn writeManifest(self: *Manifest) !void {
11321137 assert(self.have_exclusive_lock);
1133
1138 const io = self.cache.io;
11341139 const manifest_file = self.manifest_file.?;
11351140 if (self.manifest_dirty) {
11361141 self.manifest_dirty = false;
11371142
11381143 var buffer: [4000]u8 = undefined;
1139 var fw = manifest_file.writer(&buffer);
1144 var fw = manifest_file.writer(io, &buffer);
11401145 writeDirtyManifestToStream(self, &fw) catch |err| switch (err) {
11411146 error.WriteFailed => return fw.err.?,
11421147 else => |e| return e,
......@@ -1148,7 +1153,7 @@ pub const Manifest = struct {
11481153 }
11491154 }
11501155
1151 fn writeDirtyManifestToStream(self: *Manifest, fw: *fs.File.Writer) !void {
1156 fn writeDirtyManifestToStream(self: *Manifest, fw: *Io.File.Writer) !void {
11521157 try fw.interface.writeAll(manifest_header ++ "\n");
11531158 for (self.files.keys()) |file| {
11541159 try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{
......@@ -1165,13 +1170,11 @@ pub const Manifest = struct {
11651170
11661171 fn downgradeToSharedLock(self: *Manifest) !void {
11671172 if (!self.have_exclusive_lock) return;
1173 const io = self.cache.io;
11681174
1169 // WASI does not currently support flock, so we bypass it here.
1170 // TODO: If/when flock is supported on WASI, this check should be removed.
1171 // See https://github.com/WebAssembly/wasi-filesystem/issues/2
1172 if (builtin.os.tag != .wasi or std.process.can_spawn or !builtin.single_threaded) {
1175 if (std.process.can_spawn or !builtin.single_threaded) {
11731176 const manifest_file = self.manifest_file.?;
1174 try manifest_file.downgradeLock();
1177 try manifest_file.downgradeLock(io);
11751178 }
11761179
11771180 self.have_exclusive_lock = false;
......@@ -1180,16 +1183,14 @@ pub const Manifest = struct {
11801183 fn upgradeToExclusiveLock(self: *Manifest) error{CacheCheckFailed}!bool {
11811184 if (self.have_exclusive_lock) return false;
11821185 assert(self.manifest_file != null);
1186 const io = self.cache.io;
11831187
1184 // WASI does not currently support flock, so we bypass it here.
1185 // TODO: If/when flock is supported on WASI, this check should be removed.
1186 // See https://github.com/WebAssembly/wasi-filesystem/issues/2
1187 if (builtin.os.tag != .wasi or std.process.can_spawn or !builtin.single_threaded) {
1188 if (std.process.can_spawn or !builtin.single_threaded) {
11881189 const manifest_file = self.manifest_file.?;
11891190 // Here we intentionally have a period where the lock is released, in case there are
11901191 // other processes holding a shared lock.
1191 manifest_file.unlock();
1192 manifest_file.lock(.exclusive) catch |err| {
1192 manifest_file.unlock(io);
1193 manifest_file.lock(io, .exclusive) catch |err| {
11931194 self.diagnostic = .{ .manifest_lock = err };
11941195 return error.CacheCheckFailed;
11951196 };
......@@ -1202,25 +1203,23 @@ pub const Manifest = struct {
12021203 /// The `Manifest` remains safe to deinit.
12031204 /// Don't forget to call `writeManifest` before this!
12041205 pub fn toOwnedLock(self: *Manifest) Lock {
1205 const lock: Lock = .{
1206 .manifest_file = self.manifest_file.?,
1207 };
1208
1209 self.manifest_file = null;
1210 return lock;
1206 defer self.manifest_file = null;
1207 return .{ .manifest_file = self.manifest_file.? };
12111208 }
12121209
12131210 /// Releases the manifest file and frees any memory the Manifest was using.
12141211 /// `Manifest.hit` must be called first.
12151212 /// Don't forget to call `writeManifest` before this!
12161213 pub fn deinit(self: *Manifest) void {
1214 const io = self.cache.io;
1215
12171216 if (self.manifest_file) |file| {
12181217 if (builtin.os.tag == .windows) {
12191218 // See Lock.release for why this is required on Windows
1220 file.unlock();
1219 file.unlock(io);
12211220 }
12221221
1223 file.close();
1222 file.close(io);
12241223 }
12251224 for (self.files.keys()) |*file| {
12261225 file.deinit(self.cache.gpa);
......@@ -1278,57 +1277,33 @@ pub const Manifest = struct {
12781277 }
12791278};
12801279
1281/// On operating systems that support symlinks, does a readlink. On other operating systems,
1282/// uses the file contents. Windows supports symlinks but only with elevated privileges, so
1283/// it is treated as not supporting symlinks.
1284pub fn readSmallFile(dir: fs.Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1285 if (builtin.os.tag == .windows) {
1286 return dir.readFile(sub_path, buffer);
1287 } else {
1288 return dir.readLink(sub_path, buffer);
1289 }
1290}
1291
1292/// On operating systems that support symlinks, does a symlink. On other operating systems,
1293/// uses the file contents. Windows supports symlinks but only with elevated privileges, so
1294/// it is treated as not supporting symlinks.
1295/// `data` must be a valid UTF-8 encoded file path and 255 bytes or fewer.
1296pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void {
1297 assert(data.len <= 255);
1298 if (builtin.os.tag == .windows) {
1299 return dir.writeFile(.{ .sub_path = sub_path, .data = data });
1300 } else {
1301 return dir.symLink(data, sub_path, .{});
1302 }
1303}
1304
1305fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadError!void {
1306 var buf: [1024]u8 = undefined;
1280fn hashFile(io: Io, file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.ReadPositionalError!void {
1281 var buffer: [2048]u8 = undefined;
13071282 var hasher = hasher_init;
1308 var off: u64 = 0;
1283 var offset: u64 = 0;
13091284 while (true) {
1310 const bytes_read = try file.pread(&buf, off);
1311 if (bytes_read == 0) break;
1312 hasher.update(buf[0..bytes_read]);
1313 off += bytes_read;
1285 const n = try file.readPositional(io, &.{&buffer}, offset);
1286 if (n == 0) break;
1287 hasher.update(buffer[0..n]);
1288 offset += n;
13141289 }
13151290 hasher.final(bin_digest);
13161291}
13171292
13181293// Create/Write a file, close it, then grab its stat.mtime timestamp.
1319fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp {
1294fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {
13201295 const test_out_file = "test-filetimestamp.tmp";
13211296
1322 var file = try dir.createFile(test_out_file, .{
1297 var file = try dir.createFile(io, test_out_file, .{
13231298 .read = true,
13241299 .truncate = true,
13251300 });
13261301 defer {
1327 file.close();
1328 dir.deleteFile(test_out_file) catch {};
1302 file.close(io);
1303 dir.deleteFile(io, test_out_file) catch {};
13291304 }
13301305
1331 return (try file.stat()).mtime;
1306 return (try file.stat(io)).mtime;
13321307}
13331308
13341309test "cache file and then recall it" {
......@@ -1340,11 +1315,11 @@ test "cache file and then recall it" {
13401315 const temp_file = "test.txt";
13411316 const temp_manifest_dir = "temp_manifest_dir";
13421317
1343 try tmp.dir.writeFile(.{ .sub_path = temp_file, .data = "Hello, world!\n" });
1318 try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = "Hello, world!\n" });
13441319
13451320 // Wait for file timestamps to tick
1346 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1347 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1321 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
1322 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
13481323 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
13491324 }
13501325
......@@ -1355,10 +1330,10 @@ test "cache file and then recall it" {
13551330 var cache: Cache = .{
13561331 .io = io,
13571332 .gpa = testing.allocator,
1358 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1333 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
13591334 };
13601335 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1361 defer cache.manifest_dir.close();
1336 defer cache.manifest_dir.close(io);
13621337
13631338 {
13641339 var ch = cache.obtain();
......@@ -1406,11 +1381,11 @@ test "check that changing a file makes cache fail" {
14061381 const original_temp_file_contents = "Hello, world!\n";
14071382 const updated_temp_file_contents = "Hello, world; but updated!\n";
14081383
1409 try tmp.dir.writeFile(.{ .sub_path = temp_file, .data = original_temp_file_contents });
1384 try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = original_temp_file_contents });
14101385
14111386 // Wait for file timestamps to tick
1412 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1413 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1387 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
1388 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
14141389 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
14151390 }
14161391
......@@ -1421,10 +1396,10 @@ test "check that changing a file makes cache fail" {
14211396 var cache: Cache = .{
14221397 .io = io,
14231398 .gpa = testing.allocator,
1424 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1399 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
14251400 };
14261401 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1427 defer cache.manifest_dir.close();
1402 defer cache.manifest_dir.close(io);
14281403
14291404 {
14301405 var ch = cache.obtain();
......@@ -1443,7 +1418,7 @@ test "check that changing a file makes cache fail" {
14431418 try ch.writeManifest();
14441419 }
14451420
1446 try tmp.dir.writeFile(.{ .sub_path = temp_file, .data = updated_temp_file_contents });
1421 try tmp.dir.writeFile(io, .{ .sub_path = temp_file, .data = updated_temp_file_contents });
14471422
14481423 {
14491424 var ch = cache.obtain();
......@@ -1481,10 +1456,10 @@ test "no file inputs" {
14811456 var cache: Cache = .{
14821457 .io = io,
14831458 .gpa = testing.allocator,
1484 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1459 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
14851460 };
14861461 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1487 defer cache.manifest_dir.close();
1462 defer cache.manifest_dir.close(io);
14881463
14891464 {
14901465 var man = cache.obtain();
......@@ -1523,12 +1498,12 @@ test "Manifest with files added after initial hash work" {
15231498 const temp_file2 = "cache_hash_post_file_test2.txt";
15241499 const temp_manifest_dir = "cache_hash_post_file_manifest_dir";
15251500
1526 try tmp.dir.writeFile(.{ .sub_path = temp_file1, .data = "Hello, world!\n" });
1527 try tmp.dir.writeFile(.{ .sub_path = temp_file2, .data = "Hello world the second!\n" });
1501 try tmp.dir.writeFile(io, .{ .sub_path = temp_file1, .data = "Hello, world!\n" });
1502 try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second!\n" });
15281503
15291504 // Wait for file timestamps to tick
1530 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1531 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1505 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
1506 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
15321507 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
15331508 }
15341509
......@@ -1540,10 +1515,10 @@ test "Manifest with files added after initial hash work" {
15401515 var cache: Cache = .{
15411516 .io = io,
15421517 .gpa = testing.allocator,
1543 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1518 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
15441519 };
15451520 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1546 defer cache.manifest_dir.close();
1521 defer cache.manifest_dir.close(io);
15471522
15481523 {
15491524 var ch = cache.obtain();
......@@ -1575,11 +1550,11 @@ test "Manifest with files added after initial hash work" {
15751550 try testing.expect(mem.eql(u8, &digest1, &digest2));
15761551
15771552 // Modify the file added after initial hash
1578 try tmp.dir.writeFile(.{ .sub_path = temp_file2, .data = "Hello world the second, updated\n" });
1553 try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second, updated\n" });
15791554
15801555 // Wait for file timestamps to tick
1581 const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
1582 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time2.nanoseconds) {
1556 const initial_time2 = try testGetCurrentFileTimestamp(io, tmp.dir);
1557 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time2.nanoseconds) {
15831558 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
15841559 }
15851560
lib/std/Build/Cache/Directory.zig+8-6
......@@ -1,7 +1,9 @@
11const Directory = @This();
2
23const std = @import("../../std.zig");
3const assert = std.debug.assert;
4const Io = std.Io;
45const fs = std.fs;
6const assert = std.debug.assert;
57const fmt = std.fmt;
68const Allocator = std.mem.Allocator;
79
......@@ -9,7 +11,7 @@ const Allocator = std.mem.Allocator;
911/// directly, but it is needed when passing the directory to a child process.
1012/// `null` means cwd.
1113path: ?[]const u8,
12handle: fs.Dir,
14handle: Io.Dir,
1315
1416pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory {
1517 return .{
......@@ -21,7 +23,7 @@ pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory {
2123pub fn cwd() Directory {
2224 return .{
2325 .path = null,
24 .handle = fs.cwd(),
26 .handle = .cwd(),
2527 };
2628}
2729
......@@ -50,8 +52,8 @@ pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) !
5052/// Whether or not the handle should be closed, or the path should be freed
5153/// is determined by usage, however this function is provided for convenience
5254/// if it happens to be what the caller needs.
53pub fn closeAndFree(self: *Directory, gpa: Allocator) void {
54 self.handle.close();
55pub fn closeAndFree(self: *Directory, gpa: Allocator, io: Io) void {
56 self.handle.close(io);
5557 if (self.path) |p| gpa.free(p);
5658 self.* = undefined;
5759}
......@@ -64,5 +66,5 @@ pub fn format(self: Directory, writer: *std.Io.Writer) std.Io.Writer.Error!void
6466}
6567
6668pub fn eql(self: Directory, other: Directory) bool {
67 return self.handle.fd == other.handle.fd;
69 return self.handle.handle == other.handle.handle;
6870}
lib/std/Build/Cache/Path.zig+22-24
......@@ -2,8 +2,8 @@ const Path = @This();
22
33const std = @import("../../std.zig");
44const Io = std.Io;
5const assert = std.debug.assert;
65const fs = std.fs;
6const assert = std.debug.assert;
77const Allocator = std.mem.Allocator;
88const Cache = std.Build.Cache;
99
......@@ -59,58 +59,56 @@ pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Erro
5959 return p.root_dir.joinZ(gpa, parts);
6060}
6161
62pub fn openFile(
63 p: Path,
64 sub_path: []const u8,
65 flags: fs.File.OpenFlags,
66) !fs.File {
62pub fn openFile(p: Path, io: Io, sub_path: []const u8, flags: Io.File.OpenFlags) !Io.File {
6763 var buf: [fs.max_path_bytes]u8 = undefined;
6864 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
6965 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
7066 p.sub_path, sub_path,
7167 }) catch return error.NameTooLong;
7268 };
73 return p.root_dir.handle.openFile(joined_path, flags);
69 return p.root_dir.handle.openFile(io, joined_path, flags);
7470}
7571
7672pub fn openDir(
7773 p: Path,
74 io: Io,
7875 sub_path: []const u8,
79 args: fs.Dir.OpenOptions,
80) fs.Dir.OpenError!fs.Dir {
76 args: Io.Dir.OpenOptions,
77) Io.Dir.OpenError!Io.Dir {
8178 var buf: [fs.max_path_bytes]u8 = undefined;
8279 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
8380 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
8481 p.sub_path, sub_path,
8582 }) catch return error.NameTooLong;
8683 };
87 return p.root_dir.handle.openDir(joined_path, args);
84 return p.root_dir.handle.openDir(io, joined_path, args);
8885}
8986
90pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir {
87pub fn createDirPathOpen(p: Path, io: Io, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir {
9188 var buf: [fs.max_path_bytes]u8 = undefined;
9289 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
9390 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
9491 p.sub_path, sub_path,
9592 }) catch return error.NameTooLong;
9693 };
97 return p.root_dir.handle.makeOpenPath(joined_path, opts);
94 return p.root_dir.handle.createDirPathOpen(io, joined_path, opts);
9895}
9996
100pub fn statFile(p: Path, sub_path: []const u8) !fs.Dir.Stat {
97pub fn statFile(p: Path, io: Io, sub_path: []const u8) !Io.Dir.Stat {
10198 var buf: [fs.max_path_bytes]u8 = undefined;
10299 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
103100 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
104101 p.sub_path, sub_path,
105102 }) catch return error.NameTooLong;
106103 };
107 return p.root_dir.handle.statFile(joined_path);
104 return p.root_dir.handle.statFile(io, joined_path, .{});
108105}
109106
110107pub fn atomicFile(
111108 p: Path,
109 io: Io,
112110 sub_path: []const u8,
113 options: fs.Dir.AtomicFileOptions,
111 options: Io.Dir.AtomicFileOptions,
114112 buf: *[fs.max_path_bytes]u8,
115113) !fs.AtomicFile {
116114 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
......@@ -118,27 +116,27 @@ pub fn atomicFile(
118116 p.sub_path, sub_path,
119117 }) catch return error.NameTooLong;
120118 };
121 return p.root_dir.handle.atomicFile(joined_path, options);
119 return p.root_dir.handle.atomicFile(io, joined_path, options);
122120}
123121
124pub fn access(p: Path, sub_path: []const u8, flags: Io.Dir.AccessOptions) !void {
122pub fn access(p: Path, io: Io, sub_path: []const u8, flags: Io.Dir.AccessOptions) !void {
125123 var buf: [fs.max_path_bytes]u8 = undefined;
126124 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
127125 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
128126 p.sub_path, sub_path,
129127 }) catch return error.NameTooLong;
130128 };
131 return p.root_dir.handle.access(joined_path, flags);
129 return p.root_dir.handle.access(io, joined_path, flags);
132130}
133131
134pub fn makePath(p: Path, sub_path: []const u8) !void {
132pub fn createDirPath(p: Path, io: Io, sub_path: []const u8) !void {
135133 var buf: [fs.max_path_bytes]u8 = undefined;
136134 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
137135 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
138136 p.sub_path, sub_path,
139137 }) catch return error.NameTooLong;
140138 };
141 return p.root_dir.handle.makePath(joined_path);
139 return p.root_dir.handle.createDirPath(io, joined_path);
142140}
143141
144142pub fn toString(p: Path, allocator: Allocator) Allocator.Error![]u8 {
......@@ -180,7 +178,7 @@ pub fn formatEscapeChar(path: Path, writer: *Io.Writer) Io.Writer.Error!void {
180178}
181179
182180pub fn format(self: Path, writer: *Io.Writer) Io.Writer.Error!void {
183 if (std.fs.path.isAbsolute(self.sub_path)) {
181 if (fs.path.isAbsolute(self.sub_path)) {
184182 try writer.writeAll(self.sub_path);
185183 return;
186184 }
......@@ -225,9 +223,9 @@ pub const TableAdapter = struct {
225223
226224 pub fn hash(self: TableAdapter, a: Cache.Path) u32 {
227225 _ = self;
228 const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.fd))) {
229 .pointer => @intFromPtr(a.root_dir.handle.fd),
230 .int => @as(u32, @bitCast(a.root_dir.handle.fd)),
226 const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.handle))) {
227 .pointer => @intFromPtr(a.root_dir.handle.handle),
228 .int => @as(u32, @bitCast(a.root_dir.handle.handle)),
231229 else => @compileError("unimplemented hash function"),
232230 };
233231 return @truncate(Hash.hash(seed, a.sub_path));
lib/std/Build/Fuzz.zig+35-34
......@@ -9,14 +9,12 @@ const Allocator = std.mem.Allocator;
99const log = std.log;
1010const Coverage = std.debug.Coverage;
1111const abi = Build.abi.fuzz;
12const tty = std.Io.tty;
1312
1413const Fuzz = @This();
1514const build_runner = @import("root");
1615
1716gpa: Allocator,
1817io: Io,
19ttyconf: tty.Config,
2018mode: Mode,
2119
2220/// Allocated into `gpa`.
......@@ -77,7 +75,6 @@ const CoverageMap = struct {
7775pub fn init(
7876 gpa: Allocator,
7977 io: Io,
80 ttyconf: tty.Config,
8178 all_steps: []const *Build.Step,
8279 root_prog_node: std.Progress.Node,
8380 mode: Mode,
......@@ -95,7 +92,7 @@ pub fn init(
9592 if (run.producer == null) continue;
9693 if (run.fuzz_tests.items.len == 0) continue;
9794 try steps.append(gpa, run);
98 rebuild_group.async(io, rebuildTestsWorkerRun, .{ run, gpa, ttyconf, rebuild_node });
95 rebuild_group.async(io, rebuildTestsWorkerRun, .{ run, gpa, rebuild_node });
9996 }
10097
10198 if (steps.items.len == 0) fatal("no fuzz tests found", .{});
......@@ -115,7 +112,6 @@ pub fn init(
115112 return .{
116113 .gpa = gpa,
117114 .io = io,
118 .ttyconf = ttyconf,
119115 .mode = mode,
120116 .run_steps = run_steps,
121117 .group = .init,
......@@ -154,14 +150,16 @@ pub fn deinit(fuzz: *Fuzz) void {
154150 fuzz.gpa.free(fuzz.run_steps);
155151}
156152
157fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, ttyconf: tty.Config, parent_prog_node: std.Progress.Node) void {
158 rebuildTestsWorkerRunFallible(run, gpa, ttyconf, parent_prog_node) catch |err| {
153fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) void {
154 rebuildTestsWorkerRunFallible(run, gpa, parent_prog_node) catch |err| {
159155 const compile = run.producer.?;
160156 log.err("step '{s}': failed to rebuild in fuzz mode: {t}", .{ compile.step.name, err });
161157 };
162158}
163159
164fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Config, parent_prog_node: std.Progress.Node) !void {
160fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) !void {
161 const graph = run.step.owner.graph;
162 const io = graph.io;
165163 const compile = run.producer.?;
166164 const prog_node = parent_prog_node.start(compile.step.name, 0);
167165 defer prog_node.end();
......@@ -174,9 +172,9 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Co
174172
175173 if (show_error_msgs or show_compile_errors or show_stderr) {
176174 var buf: [256]u8 = undefined;
177 const w, _ = std.debug.lockStderrWriter(&buf);
178 defer std.debug.unlockStderrWriter();
179 build_runner.printErrorMessages(gpa, &compile.step, .{}, w, ttyconf, .verbose, .indent) catch {};
175 const stderr = try io.lockStderr(&buf, graph.stderr_mode);
176 defer io.unlockStderr();
177 build_runner.printErrorMessages(gpa, &compile.step, .{}, stderr.terminal(), .verbose, .indent) catch {};
180178 }
181179
182180 const rebuilt_bin_path = result catch |err| switch (err) {
......@@ -186,12 +184,11 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Co
186184 run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename);
187185}
188186
189fn fuzzWorkerRun(
190 fuzz: *Fuzz,
191 run: *Step.Run,
192 unit_test_index: u32,
193) void {
194 const gpa = run.step.owner.allocator;
187fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_index: u32) void {
188 const owner = run.step.owner;
189 const gpa = owner.allocator;
190 const graph = owner.graph;
191 const io = graph.io;
195192 const test_name = run.cached_test_metadata.?.testName(unit_test_index);
196193
197194 const prog_node = fuzz.prog_node.start(test_name, 0);
......@@ -200,9 +197,11 @@ fn fuzzWorkerRun(
200197 run.rerunInFuzzMode(fuzz, unit_test_index, prog_node) catch |err| switch (err) {
201198 error.MakeFailed => {
202199 var buf: [256]u8 = undefined;
203 const w, _ = std.debug.lockStderrWriter(&buf);
204 defer std.debug.unlockStderrWriter();
205 build_runner.printErrorMessages(gpa, &run.step, .{}, w, fuzz.ttyconf, .verbose, .indent) catch {};
200 const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) {
201 error.Canceled => return,
202 };
203 defer io.unlockStderr();
204 build_runner.printErrorMessages(gpa, &run.step, .{}, stderr.terminal(), .verbose, .indent) catch {};
206205 return;
207206 },
208207 else => {
......@@ -360,12 +359,13 @@ fn coverageRunCancelable(fuzz: *Fuzz) Io.Cancelable!void {
360359fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutOfMemory, AlreadyReported, Canceled }!void {
361360 assert(fuzz.mode == .forever);
362361 const ws = fuzz.mode.forever.ws;
362 const gpa = fuzz.gpa;
363363 const io = fuzz.io;
364364
365365 try fuzz.coverage_mutex.lock(io);
366366 defer fuzz.coverage_mutex.unlock(io);
367367
368 const gop = try fuzz.coverage_files.getOrPut(fuzz.gpa, coverage_id);
368 const gop = try fuzz.coverage_files.getOrPut(gpa, coverage_id);
369369 if (gop.found_existing) {
370370 // We are fuzzing the same executable with multiple threads.
371371 // Perhaps the same unit test; perhaps a different one. In any
......@@ -383,12 +383,13 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
383383 .entry_points = .{},
384384 .start_timestamp = ws.now(),
385385 };
386 errdefer gop.value_ptr.coverage.deinit(fuzz.gpa);
386 errdefer gop.value_ptr.coverage.deinit(gpa);
387387
388388 const rebuilt_exe_path = run_step.rebuilt_executable.?;
389389 const target = run_step.producer.?.rootModuleTarget();
390390 var debug_info = std.debug.Info.load(
391 fuzz.gpa,
391 gpa,
392 io,
392393 rebuilt_exe_path,
393394 &gop.value_ptr.coverage,
394395 target.ofmt,
......@@ -399,21 +400,21 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
399400 });
400401 return error.AlreadyReported;
401402 };
402 defer debug_info.deinit(fuzz.gpa);
403 defer debug_info.deinit(gpa);
403404
404405 const coverage_file_path: Build.Cache.Path = .{
405406 .root_dir = run_step.step.owner.cache_root,
406407 .sub_path = "v/" ++ std.fmt.hex(coverage_id),
407408 };
408 var coverage_file = coverage_file_path.root_dir.handle.openFile(coverage_file_path.sub_path, .{}) catch |err| {
409 var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| {
409410 log.err("step '{s}': failed to load coverage file '{f}': {t}", .{
410411 run_step.step.name, coverage_file_path, err,
411412 });
412413 return error.AlreadyReported;
413414 };
414 defer coverage_file.close();
415 defer coverage_file.close(io);
415416
416 const file_size = coverage_file.getEndPos() catch |err| {
417 const file_size = coverage_file.length(io) catch |err| {
417418 log.err("unable to check len of coverage file '{f}': {t}", .{ coverage_file_path, err });
418419 return error.AlreadyReported;
419420 };
......@@ -433,14 +434,14 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
433434
434435 const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
435436 const pcs = header.pcAddrs();
436 const source_locations = try fuzz.gpa.alloc(Coverage.SourceLocation, pcs.len);
437 errdefer fuzz.gpa.free(source_locations);
437 const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len);
438 errdefer gpa.free(source_locations);
438439
439440 // Unfortunately the PCs array that LLVM gives us from the 8-bit PC
440441 // counters feature is not sorted.
441442 var sorted_pcs: std.MultiArrayList(struct { pc: u64, index: u32, sl: Coverage.SourceLocation }) = .{};
442 defer sorted_pcs.deinit(fuzz.gpa);
443 try sorted_pcs.resize(fuzz.gpa, pcs.len);
443 defer sorted_pcs.deinit(gpa);
444 try sorted_pcs.resize(gpa, pcs.len);
444445 @memcpy(sorted_pcs.items(.pc), pcs);
445446 for (sorted_pcs.items(.index), 0..) |*v, i| v.* = @intCast(i);
446447 sorted_pcs.sortUnstable(struct {
......@@ -451,7 +452,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
451452 }
452453 }{ .addrs = sorted_pcs.items(.pc) });
453454
454 debug_info.resolveAddresses(fuzz.gpa, sorted_pcs.items(.pc), sorted_pcs.items(.sl)) catch |err| {
455 debug_info.resolveAddresses(gpa, io, sorted_pcs.items(.pc), sorted_pcs.items(.sl)) catch |err| {
455456 log.err("failed to resolve addresses to source locations: {t}", .{err});
456457 return error.AlreadyReported;
457458 };
......@@ -528,12 +529,12 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {
528529 .root_dir = cov.run.step.owner.cache_root,
529530 .sub_path = "v/" ++ std.fmt.hex(cov.id),
530531 };
531 var coverage_file = coverage_file_path.root_dir.handle.openFile(coverage_file_path.sub_path, .{}) catch |err| {
532 var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| {
532533 fatal("step '{s}': failed to load coverage file '{f}': {t}", .{
533534 cov.run.step.name, coverage_file_path, err,
534535 });
535536 };
536 defer coverage_file.close();
537 defer coverage_file.close(io);
537538
538539 const fuzz_abi = std.Build.abi.fuzz;
539540 var rbuf: [0x1000]u8 = undefined;
lib/std/Build/Step.zig+29-29
......@@ -117,7 +117,6 @@ pub const MakeOptions = struct {
117117 // it currently breaks because `std.net.Address` doesn't work there. Work around for now.
118118 .wasm32 => void,
119119 },
120 ttyconf: std.Io.tty.Config,
121120 /// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds.
122121 unit_test_timeout_ns: ?u64,
123122 /// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`.
......@@ -329,16 +328,17 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
329328}
330329
331330/// For debugging purposes, prints identifying information about this Step.
332pub fn dump(step: *Step, w: *Io.Writer, tty_config: Io.tty.Config) void {
331pub fn dump(step: *Step, t: Io.Terminal) void {
332 const w = t.writer;
333333 if (step.debug_stack_trace.instruction_addresses.len > 0) {
334334 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
335 std.debug.writeStackTrace(&step.debug_stack_trace, w, tty_config) catch {};
335 std.debug.writeStackTrace(&step.debug_stack_trace, t) catch {};
336336 } else {
337337 const field = "debug_stack_frames_count";
338338 comptime assert(@hasField(Build, field));
339 tty_config.setColor(w, .yellow) catch {};
339 t.setColor(.yellow) catch {};
340340 w.print("name: '{s}'. no stack trace collected for this step, see std.Build." ++ field ++ "\n", .{step.name}) catch {};
341 tty_config.setColor(w, .reset) catch {};
341 t.setColor(.reset) catch {};
342342 }
343343}
344344
......@@ -350,6 +350,7 @@ pub fn captureChildProcess(
350350 argv: []const []const u8,
351351) !std.process.Child.RunResult {
352352 const arena = s.owner.allocator;
353 const io = s.owner.graph.io;
353354
354355 // If an error occurs, it's happened in this command:
355356 assert(s.result_failed_command == null);
......@@ -358,8 +359,7 @@ pub fn captureChildProcess(
358359 try handleChildProcUnsupported(s);
359360 try handleVerbose(s.owner, null, argv);
360361
361 const result = std.process.Child.run(.{
362 .allocator = arena,
362 const result = std.process.Child.run(arena, io, .{
363363 .argv = argv,
364364 .progress_node = progress_node,
365365 }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err });
......@@ -401,6 +401,9 @@ pub fn evalZigProcess(
401401 web_server: ?*Build.WebServer,
402402 gpa: Allocator,
403403) !?Path {
404 const b = s.owner;
405 const io = b.graph.io;
406
404407 // If an error occurs, it's happened in this command:
405408 assert(s.result_failed_command == null);
406409 s.result_failed_command = try allocPrintCmd(gpa, null, argv);
......@@ -411,7 +414,7 @@ pub fn evalZigProcess(
411414 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
412415 error.BrokenPipe => {
413416 // Process restart required.
414 const term = zp.child.wait() catch |e| {
417 const term = zp.child.wait(io) catch |e| {
415418 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
416419 };
417420 _ = term;
......@@ -427,7 +430,7 @@ pub fn evalZigProcess(
427430
428431 if (s.result_error_msgs.items.len > 0 and result == null) {
429432 // Crash detected.
430 const term = zp.child.wait() catch |e| {
433 const term = zp.child.wait(io) catch |e| {
431434 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
432435 };
433436 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
......@@ -439,7 +442,6 @@ pub fn evalZigProcess(
439442 return result;
440443 }
441444 assert(argv.len != 0);
442 const b = s.owner;
443445 const arena = b.allocator;
444446
445447 try handleChildProcUnsupported(s);
......@@ -453,7 +455,7 @@ pub fn evalZigProcess(
453455 child.request_resource_usage_statistics = true;
454456 child.progress_node = prog_node;
455457
456 child.spawn() catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
458 child.spawn(io) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
457459
458460 const zp = try gpa.create(ZigProcess);
459461 zp.* = .{
......@@ -474,10 +476,10 @@ pub fn evalZigProcess(
474476
475477 if (!watch) {
476478 // Send EOF to stdin.
477 zp.child.stdin.?.close();
479 zp.child.stdin.?.close(io);
478480 zp.child.stdin = null;
479481
480 const term = zp.child.wait() catch |err| {
482 const term = zp.child.wait(io) catch |err| {
481483 return s.fail("unable to wait for {s}: {t}", .{ argv[0], err });
482484 };
483485 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
......@@ -504,36 +506,34 @@ pub fn evalZigProcess(
504506 return result;
505507}
506508
507/// Wrapper around `std.fs.Dir.updateFile` that handles verbose and error output.
509/// Wrapper around `Io.Dir.updateFile` that handles verbose and error output.
508510pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus {
509511 const b = s.owner;
510512 const io = b.graph.io;
511513 const src_path = src_lazy_path.getPath3(b, s);
512514 try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });
513 return Io.Dir.updateFile(src_path.root_dir.handle.adaptToNewApi(), io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| {
514 return s.fail("unable to update file from '{f}' to '{s}': {t}", .{
515 src_path, dest_path, err,
516 });
517 };
515 return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err|
516 return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err });
518517}
519518
520/// Wrapper around `std.fs.Dir.makePathStatus` that handles verbose and error output.
521pub fn installDir(s: *Step, dest_path: []const u8) !std.fs.Dir.MakePathStatus {
519/// Wrapper around `Io.Dir.createDirPathStatus` that handles verbose and error output.
520pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.CreatePathStatus {
522521 const b = s.owner;
522 const io = b.graph.io;
523523 try handleVerbose(b, null, &.{ "install", "-d", dest_path });
524 return std.fs.cwd().makePathStatus(dest_path) catch |err| {
524 return Io.Dir.cwd().createDirPathStatus(io, dest_path, .default_dir) catch |err|
525525 return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err });
526 };
527526}
528527
529528fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.WebServer, gpa: Allocator) !?Path {
530529 const b = s.owner;
531530 const arena = b.allocator;
531 const io = b.graph.io;
532532
533533 var timer = try std.time.Timer.start();
534534
535 try sendMessage(zp.child.stdin.?, .update);
536 if (!watch) try sendMessage(zp.child.stdin.?, .exit);
535 try sendMessage(io, zp.child.stdin.?, .update);
536 if (!watch) try sendMessage(io, zp.child.stdin.?, .exit);
537537
538538 var result: ?Path = null;
539539
......@@ -670,12 +670,12 @@ fn clearZigProcess(s: *Step, gpa: Allocator) void {
670670 }
671671}
672672
673fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
673fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
674674 const header: std.zig.Client.Message.Header = .{
675675 .tag = tag,
676676 .bytes_len = 0,
677677 };
678 var w = file.writer(&.{});
678 var w = file.writer(io, &.{});
679679 w.interface.writeStruct(header, .little) catch |err| switch (err) {
680680 error.WriteFailed => return w.err.?,
681681 };
......@@ -898,7 +898,7 @@ pub fn addWatchInput(step: *Step, lazy_file: Build.LazyPath) Allocator.Error!voi
898898 try addWatchInputFromPath(step, .{
899899 .root_dir = .{
900900 .path = null,
901 .handle = std.fs.cwd(),
901 .handle = Io.Dir.cwd(),
902902 },
903903 .sub_path = std.fs.path.dirname(path_string) orelse "",
904904 }, std.fs.path.basename(path_string));
......@@ -923,7 +923,7 @@ pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Alloc
923923 try addDirectoryWatchInputFromPath(step, .{
924924 .root_dir = .{
925925 .path = null,
926 .handle = std.fs.cwd(),
926 .handle = Io.Dir.cwd(),
927927 },
928928 .sub_path = path_string,
929929 });
lib/std/Build/Step/CheckFile.zig+4-1
......@@ -3,7 +3,9 @@
33//! TODO: generalize the code in std.testing.expectEqualStrings and make this
44//! CheckFile step produce those helpful diagnostics when there is not a match.
55const CheckFile = @This();
6
67const std = @import("std");
8const Io = std.Io;
79const Step = std.Build.Step;
810const fs = std.fs;
911const mem = std.mem;
......@@ -49,11 +51,12 @@ pub fn setName(check_file: *CheckFile, name: []const u8) void {
4951fn make(step: *Step, options: Step.MakeOptions) !void {
5052 _ = options;
5153 const b = step.owner;
54 const io = b.graph.io;
5255 const check_file: *CheckFile = @fieldParentPtr("step", step);
5356 try step.singleUnchangingWatchInput(check_file.source);
5457
5558 const src_path = check_file.source.getPath2(b, step);
56 const contents = fs.cwd().readFileAlloc(src_path, b.allocator, .limited(check_file.max_bytes)) catch |err| {
59 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, b.allocator, .limited(check_file.max_bytes)) catch |err| {
5760 return step.fail("unable to read '{s}': {s}", .{
5861 src_path, @errorName(err),
5962 });
lib/std/Build/Step/CheckObject.zig+2
......@@ -547,12 +547,14 @@ pub fn checkComputeCompare(
547547fn make(step: *Step, make_options: Step.MakeOptions) !void {
548548 _ = make_options;
549549 const b = step.owner;
550 const io = b.graph.io;
550551 const gpa = b.allocator;
551552 const check_object: *CheckObject = @fieldParentPtr("step", step);
552553 try step.singleUnchangingWatchInput(check_object.source);
553554
554555 const src_path = check_object.source.getPath3(b, step);
555556 const contents = src_path.root_dir.handle.readFileAllocOptions(
557 io,
556558 src_path.sub_path,
557559 gpa,
558560 .limited(check_object.max_bytes),
lib/std/Build/Step/Compile.zig+35-24
......@@ -1,12 +1,15 @@
1const Compile = @This();
12const builtin = @import("builtin");
3
24const std = @import("std");
5const Io = std.Io;
36const mem = std.mem;
47const fs = std.fs;
58const assert = std.debug.assert;
69const panic = std.debug.panic;
710const StringHashMap = std.StringHashMap;
811const Sha256 = std.crypto.hash.sha2.Sha256;
9const Allocator = mem.Allocator;
12const Allocator = std.mem.Allocator;
1013const Step = std.Build.Step;
1114const LazyPath = std.Build.LazyPath;
1215const PkgConfigPkg = std.Build.PkgConfigPkg;
......@@ -15,7 +18,6 @@ const RunError = std.Build.RunError;
1518const Module = std.Build.Module;
1619const InstallDir = std.Build.InstallDir;
1720const GeneratedFile = std.Build.GeneratedFile;
18const Compile = @This();
1921const Path = std.Build.Cache.Path;
2022
2123pub const base_id: Step.Id = .compile;
......@@ -920,20 +922,24 @@ const CliNamedModules = struct {
920922 }
921923};
922924
923fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking_step: ?*Step) []const u8 {
925fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking_step: ?*Step) ![]const u8 {
926 const step = &compile.step;
927 const b = step.owner;
928 const graph = b.graph;
929 const io = graph.io;
924930 const maybe_path: ?*GeneratedFile = @field(compile, tag_name);
925931
926932 const generated_file = maybe_path orelse {
927 const w, const ttyconf = std.debug.lockStderrWriter(&.{});
928 std.Build.dumpBadGetPathHelp(&compile.step, w, ttyconf, compile.step.owner, asking_step) catch {};
929 std.debug.unlockStderrWriter();
933 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
934 std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {};
935 io.unlockStderr();
930936 @panic("missing emit option for " ++ tag_name);
931937 };
932938
933939 const path = generated_file.path orelse {
934 const w, const ttyconf = std.debug.lockStderrWriter(&.{});
935 std.Build.dumpBadGetPathHelp(&compile.step, w, ttyconf, compile.step.owner, asking_step) catch {};
936 std.debug.unlockStderrWriter();
940 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
941 std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {};
942 io.unlockStderr();
937943 @panic(tag_name ++ " is null. Is there a missing step dependency?");
938944 };
939945
......@@ -1147,9 +1153,9 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
11471153 // For everything else, we directly link
11481154 // against the library file.
11491155 const full_path_lib = if (other_produces_implib)
1150 other.getGeneratedFilePath("generated_implib", &compile.step)
1156 try other.getGeneratedFilePath("generated_implib", &compile.step)
11511157 else
1152 other.getGeneratedFilePath("generated_bin", &compile.step);
1158 try other.getGeneratedFilePath("generated_bin", &compile.step);
11531159
11541160 try zig_args.append(full_path_lib);
11551161 total_linker_objects += 1;
......@@ -1561,19 +1567,22 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
15611567 }
15621568
15631569 // -I and -L arguments that appear after the last --mod argument apply to all modules.
1570 const cwd: Io.Dir = .cwd();
1571 const io = b.graph.io;
1572
15641573 for (b.search_prefixes.items) |search_prefix| {
1565 var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| {
1574 var prefix_dir = cwd.openDir(io, search_prefix, .{}) catch |err| {
15661575 return step.fail("unable to open prefix directory '{s}': {s}", .{
15671576 search_prefix, @errorName(err),
15681577 });
15691578 };
1570 defer prefix_dir.close();
1579 defer prefix_dir.close(io);
15711580
15721581 // Avoid passing -L and -I flags for nonexistent directories.
15731582 // This prevents a warning, that should probably be upgraded to an error in Zig's
15741583 // CLI parsing code, when the linker sees an -L directory that does not exist.
15751584
1576 if (prefix_dir.access("lib", .{})) |_| {
1585 if (prefix_dir.access(io, "lib", .{})) |_| {
15771586 try zig_args.appendSlice(&.{
15781587 "-L", b.pathJoin(&.{ search_prefix, "lib" }),
15791588 });
......@@ -1584,7 +1593,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
15841593 }),
15851594 }
15861595
1587 if (prefix_dir.access("include", .{})) |_| {
1596 if (prefix_dir.access(io, "include", .{})) |_| {
15881597 try zig_args.appendSlice(&.{
15891598 "-I", b.pathJoin(&.{ search_prefix, "include" }),
15901599 });
......@@ -1660,7 +1669,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
16601669 args_length += arg.len + 1; // +1 to account for null terminator
16611670 }
16621671 if (args_length >= 30 * 1024) {
1663 try b.cache_root.handle.makePath("args");
1672 try b.cache_root.handle.createDirPath(io, "args");
16641673
16651674 const args_to_escape = zig_args.items[2..];
16661675 var escaped_args = try std.array_list.Managed([]const u8).initCapacity(arena, args_to_escape.len);
......@@ -1693,18 +1702,18 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
16931702 _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash});
16941703
16951704 const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash;
1696 if (b.cache_root.handle.access(args_file, .{})) |_| {
1705 if (b.cache_root.handle.access(io, args_file, .{})) |_| {
16971706 // The args file is already present from a previous run.
16981707 } else |err| switch (err) {
16991708 error.FileNotFound => {
1700 try b.cache_root.handle.makePath("tmp");
1709 try b.cache_root.handle.createDirPath(io, "tmp");
17011710 const rand_int = std.crypto.random.int(u64);
17021711 const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
1703 try b.cache_root.handle.writeFile(.{ .sub_path = tmp_path, .data = args });
1704 defer b.cache_root.handle.deleteFile(tmp_path) catch {
1712 try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args });
1713 defer b.cache_root.handle.deleteFile(io, tmp_path) catch {
17051714 // It's fine if the temporary file can't be cleaned up.
17061715 };
1707 b.cache_root.handle.rename(tmp_path, args_file) catch |rename_err| switch (rename_err) {
1716 b.cache_root.handle.rename(tmp_path, b.cache_root.handle, args_file, io) catch |rename_err| switch (rename_err) {
17081717 error.PathAlreadyExists => {
17091718 // The args file was created by another concurrent build process.
17101719 },
......@@ -1816,18 +1825,20 @@ pub fn doAtomicSymLinks(
18161825 filename_name_only: []const u8,
18171826) !void {
18181827 const b = step.owner;
1828 const io = b.graph.io;
18191829 const out_dir = fs.path.dirname(output_path) orelse ".";
18201830 const out_basename = fs.path.basename(output_path);
18211831 // sym link for libfoo.so.1 to libfoo.so.1.2.3
18221832 const major_only_path = b.pathJoin(&.{ out_dir, filename_major_only });
1823 fs.cwd().atomicSymLink(out_basename, major_only_path, .{}) catch |err| {
1833 const cwd: Io.Dir = .cwd();
1834 cwd.symLinkAtomic(io, out_basename, major_only_path, .{}) catch |err| {
18241835 return step.fail("unable to symlink {s} -> {s}: {s}", .{
18251836 major_only_path, out_basename, @errorName(err),
18261837 });
18271838 };
18281839 // sym link for libfoo.so to libfoo.so.1
18291840 const name_only_path = b.pathJoin(&.{ out_dir, filename_name_only });
1830 fs.cwd().atomicSymLink(filename_major_only, name_only_path, .{}) catch |err| {
1841 cwd.symLinkAtomic(io, filename_major_only, name_only_path, .{}) catch |err| {
18311842 return step.fail("Unable to symlink {s} -> {s}: {s}", .{
18321843 name_only_path, filename_major_only, @errorName(err),
18331844 });
......@@ -1897,7 +1908,7 @@ fn checkCompileErrors(compile: *Compile) !void {
18971908 try actual_eb.renderToWriter(.{
18981909 .include_reference_trace = false,
18991910 .include_source_line = false,
1900 }, &aw.writer, .no_color);
1911 }, &aw.writer);
19011912 break :ae try aw.toOwnedSlice();
19021913 };
19031914
lib/std/Build/Step/ConfigHeader.zig+8-5
......@@ -1,5 +1,7 @@
1const std = @import("std");
21const ConfigHeader = @This();
2
3const std = @import("std");
4const Io = std.Io;
35const Step = std.Build.Step;
46const Allocator = std.mem.Allocator;
57const Writer = std.Io.Writer;
......@@ -182,6 +184,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
182184
183185 const gpa = b.allocator;
184186 const arena = b.allocator;
187 const io = b.graph.io;
185188
186189 var man = b.graph.cache.obtain();
187190 defer man.deinit();
......@@ -205,7 +208,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
205208 .autoconf_undef, .autoconf_at => |file_source| {
206209 try bw.writeAll(c_generated_line);
207210 const src_path = file_source.getPath2(b, step);
208 const contents = std.fs.cwd().readFileAlloc(src_path, arena, .limited(config_header.max_bytes)) catch |err| {
211 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| {
209212 return step.fail("unable to read autoconf input file '{s}': {s}", .{
210213 src_path, @errorName(err),
211214 });
......@@ -219,7 +222,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
219222 .cmake => |file_source| {
220223 try bw.writeAll(c_generated_line);
221224 const src_path = file_source.getPath2(b, step);
222 const contents = std.fs.cwd().readFileAlloc(src_path, arena, .limited(config_header.max_bytes)) catch |err| {
225 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| {
223226 return step.fail("unable to read cmake input file '{s}': {s}", .{
224227 src_path, @errorName(err),
225228 });
......@@ -255,13 +258,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
255258 const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path });
256259 const sub_path_dirname = std.fs.path.dirname(sub_path).?;
257260
258 b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
261 b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| {
259262 return step.fail("unable to make path '{f}{s}': {s}", .{
260263 b.cache_root, sub_path_dirname, @errorName(err),
261264 });
262265 };
263266
264 b.cache_root.handle.writeFile(.{ .sub_path = sub_path, .data = output }) catch |err| {
267 b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = output }) catch |err| {
265268 return step.fail("unable to write file '{f}{s}': {s}", .{
266269 b.cache_root, sub_path, @errorName(err),
267270 });
lib/std/Build/Step/InstallArtifact.zig+4-3
......@@ -119,6 +119,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
119119 _ = options;
120120 const install_artifact: *InstallArtifact = @fieldParentPtr("step", step);
121121 const b = step.owner;
122 const io = b.graph.io;
122123
123124 var all_cached = true;
124125
......@@ -163,15 +164,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
163164 const src_dir_path = dir.source.getPath3(b, step);
164165 const full_h_prefix = b.getInstallPath(h_dir, dir.dest_rel_path);
165166
166 var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
167 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
167168 return step.fail("unable to open source directory '{f}': {s}", .{
168169 src_dir_path, @errorName(err),
169170 });
170171 };
171 defer src_dir.close();
172 defer src_dir.close(io);
172173
173174 var it = try src_dir.walk(b.allocator);
174 next_entry: while (try it.next()) |entry| {
175 next_entry: while (try it.next(io)) |entry| {
175176 for (dir.options.exclude_extensions) |ext| {
176177 if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry;
177178 }
lib/std/Build/Step/InstallDir.zig+5-6
......@@ -58,21 +58,20 @@ pub fn create(owner: *std.Build, options: Options) *InstallDir {
5858fn make(step: *Step, options: Step.MakeOptions) !void {
5959 _ = options;
6060 const b = step.owner;
61 const io = b.graph.io;
6162 const install_dir: *InstallDir = @fieldParentPtr("step", step);
6263 step.clearWatchInputs();
6364 const arena = b.allocator;
6465 const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir);
6566 const src_dir_path = install_dir.options.source_dir.getPath3(b, step);
6667 const need_derived_inputs = try step.addDirectoryWatchInput(install_dir.options.source_dir);
67 var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
68 return step.fail("unable to open source directory '{f}': {s}", .{
69 src_dir_path, @errorName(err),
70 });
68 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
69 return step.fail("unable to open source directory '{f}': {t}", .{ src_dir_path, err });
7170 };
72 defer src_dir.close();
71 defer src_dir.close(io);
7372 var it = try src_dir.walk(arena);
7473 var all_cached = true;
75 next_entry: while (try it.next()) |entry| {
74 next_entry: while (try it.next(io)) |entry| {
7675 for (install_dir.options.exclude_extensions) |ext| {
7776 if (mem.endsWith(u8, entry.path, ext)) continue :next_entry;
7877 }
lib/std/Build/Step/ObjCopy.zig+3-2
......@@ -3,7 +3,7 @@ const ObjCopy = @This();
33
44const Allocator = std.mem.Allocator;
55const ArenaAllocator = std.heap.ArenaAllocator;
6const File = std.fs.File;
6const File = std.Io.File;
77const InstallDir = std.Build.InstallDir;
88const Step = std.Build.Step;
99const elf = std.elf;
......@@ -143,6 +143,7 @@ pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath {
143143fn make(step: *Step, options: Step.MakeOptions) !void {
144144 const prog_node = options.progress_node;
145145 const b = step.owner;
146 const io = b.graph.io;
146147 const objcopy: *ObjCopy = @fieldParentPtr("step", step);
147148 try step.singleUnchangingWatchInput(objcopy.input_file);
148149
......@@ -176,7 +177,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
176177 const cache_path = "o" ++ fs.path.sep_str ++ digest;
177178 const full_dest_path = try b.cache_root.join(b.allocator, &.{ cache_path, objcopy.basename });
178179 const full_dest_path_debug = try b.cache_root.join(b.allocator, &.{ cache_path, b.fmt("{s}.debug", .{objcopy.basename}) });
179 b.cache_root.handle.makePath(cache_path) catch |err| {
180 b.cache_root.handle.createDirPath(io, cache_path) catch |err| {
180181 return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });
181182 };
182183
lib/std/Build/Step/Options.zig+28-29
......@@ -1,12 +1,13 @@
1const std = @import("std");
1const Options = @This();
22const builtin = @import("builtin");
3
4const std = @import("std");
5const Io = std.Io;
36const fs = std.fs;
47const Step = std.Build.Step;
58const GeneratedFile = std.Build.GeneratedFile;
69const LazyPath = std.Build.LazyPath;
710
8const Options = @This();
9
1011pub const base_id: Step.Id = .options;
1112
1213step: Step,
......@@ -441,6 +442,7 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
441442 _ = make_options;
442443
443444 const b = step.owner;
445 const io = b.graph.io;
444446 const options: *Options = @fieldParentPtr("step", step);
445447
446448 for (options.args.items) |item| {
......@@ -468,18 +470,15 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
468470
469471 // Optimize for the hot path. Stat the file, and if it already exists,
470472 // cache hit.
471 if (b.cache_root.handle.access(sub_path, .{})) |_| {
473 if (b.cache_root.handle.access(io, sub_path, .{})) |_| {
472474 // This is the hot path, success.
473475 step.result_cached = true;
474476 return;
475477 } else |outer_err| switch (outer_err) {
476478 error.FileNotFound => {
477479 const sub_dirname = fs.path.dirname(sub_path).?;
478 b.cache_root.handle.makePath(sub_dirname) catch |e| {
479 return step.fail("unable to make path '{f}{s}': {s}", .{
480 b.cache_root, sub_dirname, @errorName(e),
481 });
482 };
480 b.cache_root.handle.createDirPath(io, sub_dirname) catch |e|
481 return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e });
483482
484483 const rand_int = std.crypto.random.int(u64);
485484 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++
......@@ -487,40 +486,40 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
487486 basename;
488487 const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?;
489488
490 b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| {
491 return step.fail("unable to make temporary directory '{f}{s}': {s}", .{
492 b.cache_root, tmp_sub_path_dirname, @errorName(err),
489 b.cache_root.handle.createDirPath(io, tmp_sub_path_dirname) catch |err| {
490 return step.fail("unable to make temporary directory '{f}{s}': {t}", .{
491 b.cache_root, tmp_sub_path_dirname, err,
493492 });
494493 };
495494
496 b.cache_root.handle.writeFile(.{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
497 return step.fail("unable to write options to '{f}{s}': {s}", .{
498 b.cache_root, tmp_sub_path, @errorName(err),
495 b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
496 return step.fail("unable to write options to '{f}{s}': {t}", .{
497 b.cache_root, tmp_sub_path, err,
499498 });
500499 };
501500
502 b.cache_root.handle.rename(tmp_sub_path, sub_path) catch |err| switch (err) {
501 b.cache_root.handle.rename(tmp_sub_path, b.cache_root.handle, sub_path, io) catch |err| switch (err) {
503502 error.PathAlreadyExists => {
504503 // Other process beat us to it. Clean up the temp file.
505 b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| {
506 try step.addError("warning: unable to delete temp file '{f}{s}': {s}", .{
507 b.cache_root, tmp_sub_path, @errorName(e),
504 b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| {
505 try step.addError("warning: unable to delete temp file '{f}{s}': {t}", .{
506 b.cache_root, tmp_sub_path, e,
508507 });
509508 };
510509 step.result_cached = true;
511510 return;
512511 },
513512 else => {
514 return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {s}", .{
515 b.cache_root, tmp_sub_path,
516 b.cache_root, sub_path,
517 @errorName(err),
513 return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {t}", .{
514 b.cache_root, tmp_sub_path,
515 b.cache_root, sub_path,
516 err,
518517 });
519518 },
520519 };
521520 },
522 else => |e| return step.fail("unable to access options file '{f}{s}': {s}", .{
523 b.cache_root, sub_path, @errorName(e),
521 else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{
522 b.cache_root, sub_path, e,
524523 }),
525524 }
526525}
......@@ -544,11 +543,11 @@ test Options {
544543 .cache = .{
545544 .io = io,
546545 .gpa = arena.allocator(),
547 .manifest_dir = std.fs.cwd(),
546 .manifest_dir = Io.Dir.cwd(),
548547 },
549548 .zig_exe = "test",
550549 .env_map = std.process.EnvMap.init(arena.allocator()),
551 .global_cache_root = .{ .path = "test", .handle = std.fs.cwd() },
550 .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() },
552551 .host = .{
553552 .query = .{},
554553 .result = try std.zig.system.resolveTargetQuery(io, .{}),
......@@ -559,8 +558,8 @@ test Options {
559558
560559 var builder = try std.Build.create(
561560 &graph,
562 .{ .path = "test", .handle = std.fs.cwd() },
563 .{ .path = "test", .handle = std.fs.cwd() },
561 .{ .path = "test", .handle = Io.Dir.cwd() },
562 .{ .path = "test", .handle = Io.Dir.cwd() },
564563 &.{},
565564 );
566565
lib/std/Build/Step/RemoveDir.zig+4-7
......@@ -27,6 +27,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
2727 _ = options;
2828
2929 const b = step.owner;
30 const io = b.graph.io;
3031 const remove_dir: *RemoveDir = @fieldParentPtr("step", step);
3132
3233 step.clearWatchInputs();
......@@ -34,15 +35,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
3435
3536 const full_doomed_path = remove_dir.doomed_path.getPath2(b, step);
3637
37 b.build_root.handle.deleteTree(full_doomed_path) catch |err| {
38 b.build_root.handle.deleteTree(io, full_doomed_path) catch |err| {
3839 if (b.build_root.path) |base| {
39 return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{
40 base, full_doomed_path, @errorName(err),
41 });
40 return step.fail("unable to recursively delete path '{s}/{s}': {t}", .{ base, full_doomed_path, err });
4241 } else {
43 return step.fail("unable to recursively delete path '{s}': {s}", .{
44 full_doomed_path, @errorName(err),
45 });
42 return step.fail("unable to recursively delete path '{s}': {t}", .{ full_doomed_path, err });
4643 }
4744 };
4845}
lib/std/Build/Step/Run.zig+130-120
......@@ -1,15 +1,16 @@
1const std = @import("std");
1const Run = @This();
22const builtin = @import("builtin");
3
4const std = @import("std");
5const Io = std.Io;
36const Build = std.Build;
4const Step = Build.Step;
5const fs = std.fs;
7const Step = std.Build.Step;
8const Dir = std.Io.Dir;
69const mem = std.mem;
710const process = std.process;
8const EnvMap = process.EnvMap;
11const EnvMap = std.process.EnvMap;
912const assert = std.debug.assert;
10const Path = Build.Cache.Path;
11
12const Run = @This();
13const Path = std.Build.Cache.Path;
1314
1415pub const base_id: Step.Id = .run;
1516
......@@ -25,19 +26,7 @@ cwd: ?Build.LazyPath,
2526env_map: ?*EnvMap,
2627
2728/// Controls the `NO_COLOR` and `CLICOLOR_FORCE` environment variables.
28color: enum {
29 /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset.
30 enable,
31 /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset.
32 disable,
33 /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`.
34 inherit,
35 /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`.
36 auto,
37 /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables.
38 /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`.
39 manual,
40} = .auto,
29color: Color = .auto,
4130
4231/// When `true` prevents `ZIG_PROGRESS` environment variable from being passed
4332/// to the child process, which otherwise would be used for the child to send
......@@ -111,6 +100,20 @@ rebuilt_executable: ?Path,
111100/// If this Run step was produced by a Compile step, it is tracked here.
112101producer: ?*Step.Compile,
113102
103pub const Color = enum {
104 /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset.
105 enable,
106 /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset.
107 disable,
108 /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`.
109 inherit,
110 /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`.
111 auto,
112 /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables.
113 /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`.
114 manual,
115};
116
114117pub const StdIn = union(enum) {
115118 none,
116119 bytes: []const u8,
......@@ -564,7 +567,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void {
564567 if (prev_path) |pp| {
565568 const new_path = b.fmt("{s}{c}{s}", .{
566569 pp,
567 if (use_wine) fs.path.delimiter_windows else fs.path.delimiter,
570 if (use_wine) Dir.path.delimiter_windows else Dir.path.delimiter,
568571 search_path,
569572 });
570573 env_map.put(key, new_path) catch @panic("OOM");
......@@ -747,7 +750,7 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {
747750fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {
748751 const b = run.step.owner;
749752 const path_str = path.toString(b.graph.arena) catch @panic("OOM");
750 if (std.fs.path.isAbsolute(path_str)) {
753 if (Dir.path.isAbsolute(path_str)) {
751754 // Absolute paths don't need changing.
752755 return path_str;
753756 }
......@@ -755,19 +758,19 @@ fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {
755758 const child_lazy_cwd = run.cwd orelse break :rel path_str;
756759 const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM");
757760 // Convert it from relative to *our* cwd, to relative to the *child's* cwd.
758 break :rel std.fs.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM");
761 break :rel Dir.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM");
759762 };
760763 // Not every path can be made relative, e.g. if the path and the child cwd are on different
761764 // disk designators on Windows. In that case, `relative` will return an absolute path which we can
762765 // just return.
763 if (std.fs.path.isAbsolute(child_cwd_rel)) {
766 if (Dir.path.isAbsolute(child_cwd_rel)) {
764767 return child_cwd_rel;
765768 }
766769 // We're not done yet. In some cases this path must be prefixed with './':
767770 // * On POSIX, the executable name cannot be a single component like 'foo'
768771 // * Some executables might treat a leading '-' like a flag, which we must avoid
769772 // There's no harm in it, so just *always* apply this prefix.
770 return std.fs.path.join(b.graph.arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");
773 return Dir.path.join(b.graph.arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");
771774}
772775
773776const IndexedOutput = struct {
......@@ -845,13 +848,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
845848 errdefer result.deinit();
846849 result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory;
847850
848 const file = file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{}) catch |err| {
851 const file = file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{}) catch |err| {
849852 return step.fail(
850853 "unable to open input file '{f}': {t}",
851854 .{ file_path, err },
852855 );
853856 };
854 defer file.close();
857 defer file.close(io);
855858
856859 var buf: [1024]u8 = undefined;
857860 var file_reader = file.reader(io, &buf);
......@@ -964,15 +967,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
964967 &digest,
965968 );
966969
967 const output_dir_path = "o" ++ fs.path.sep_str ++ &digest;
970 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;
968971 for (output_placeholders.items) |placeholder| {
969972 const output_sub_path = b.pathJoin(&.{ output_dir_path, placeholder.output.basename });
970973 const output_sub_dir_path = switch (placeholder.tag) {
971 .output_file => fs.path.dirname(output_sub_path).?,
974 .output_file => Dir.path.dirname(output_sub_path).?,
972975 .output_directory => output_sub_path,
973976 else => unreachable,
974977 };
975 b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
978 b.cache_root.handle.createDirPath(io, output_sub_dir_path) catch |err| {
976979 return step.fail("unable to make path '{f}{s}': {s}", .{
977980 b.cache_root, output_sub_dir_path, @errorName(err),
978981 });
......@@ -994,17 +997,17 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
994997
995998 // We do not know the final output paths yet, use temp paths to run the command.
996999 const rand_int = std.crypto.random.int(u64);
997 const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
1000 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
9981001
9991002 for (output_placeholders.items) |placeholder| {
10001003 const output_components = .{ tmp_dir_path, placeholder.output.basename };
10011004 const output_sub_path = b.pathJoin(&output_components);
10021005 const output_sub_dir_path = switch (placeholder.tag) {
1003 .output_file => fs.path.dirname(output_sub_path).?,
1006 .output_file => Dir.path.dirname(output_sub_path).?,
10041007 .output_directory => output_sub_path,
10051008 else => unreachable,
10061009 };
1007 b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
1010 b.cache_root.handle.createDirPath(io, output_sub_dir_path) catch |err| {
10081011 return step.fail("unable to make path '{f}{s}': {s}", .{
10091012 b.cache_root, output_sub_dir_path, @errorName(err),
10101013 });
......@@ -1022,7 +1025,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
10221025
10231026 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, options, null);
10241027
1025 const dep_file_dir = std.fs.cwd();
1028 const dep_file_dir = Dir.cwd();
10261029 const dep_file_basename = dep_output_file.generated_file.getPath2(b, step);
10271030 if (has_side_effects)
10281031 try man.addDepFile(dep_file_dir, dep_file_basename)
......@@ -1039,29 +1042,23 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
10391042
10401043 // Rename into place
10411044 if (any_output) {
1042 const o_sub_path = "o" ++ fs.path.sep_str ++ &digest;
1045 const o_sub_path = "o" ++ Dir.path.sep_str ++ &digest;
10431046
1044 b.cache_root.handle.rename(tmp_dir_path, o_sub_path) catch |err| {
1047 b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |err| {
10451048 if (err == error.PathAlreadyExists) {
1046 b.cache_root.handle.deleteTree(o_sub_path) catch |del_err| {
1047 return step.fail("unable to remove dir '{f}'{s}: {s}", .{
1048 b.cache_root,
1049 tmp_dir_path,
1050 @errorName(del_err),
1049 b.cache_root.handle.deleteTree(io, o_sub_path) catch |del_err| {
1050 return step.fail("unable to remove dir '{f}'{s}: {t}", .{
1051 b.cache_root, tmp_dir_path, del_err,
10511052 });
10521053 };
1053 b.cache_root.handle.rename(tmp_dir_path, o_sub_path) catch |retry_err| {
1054 return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{
1055 b.cache_root, tmp_dir_path,
1056 b.cache_root, o_sub_path,
1057 @errorName(retry_err),
1054 b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |retry_err| {
1055 return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{
1056 b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, retry_err,
10581057 });
10591058 };
10601059 } else {
1061 return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{
1062 b.cache_root, tmp_dir_path,
1063 b.cache_root, o_sub_path,
1064 @errorName(err),
1060 return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{
1061 b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, err,
10651062 });
10661063 }
10671064 };
......@@ -1110,8 +1107,8 @@ pub fn rerunInFuzzMode(
11101107 errdefer result.deinit();
11111108 result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory;
11121109
1113 const file = try file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{});
1114 defer file.close();
1110 const file = try file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{});
1111 defer file.close(io);
11151112
11161113 var buf: [1024]u8 = undefined;
11171114 var file_reader = file.reader(io, &buf);
......@@ -1144,12 +1141,11 @@ pub fn rerunInFuzzMode(
11441141
11451142 const has_side_effects = false;
11461143 const rand_int = std.crypto.random.int(u64);
1147 const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
1144 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
11481145 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{
11491146 .progress_node = prog_node,
11501147 .watch = undefined, // not used by `runCommand`
11511148 .web_server = null, // only needed for time reports
1152 .ttyconf = fuzz.ttyconf,
11531149 .unit_test_timeout_ns = null, // don't time out fuzz tests for now
11541150 .gpa = fuzz.gpa,
11551151 }, .{
......@@ -1240,6 +1236,7 @@ fn runCommand(
12401236 const b = step.owner;
12411237 const arena = b.allocator;
12421238 const gpa = options.gpa;
1239 const io = b.graph.io;
12431240
12441241 const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null;
12451242
......@@ -1260,33 +1257,6 @@ fn runCommand(
12601257 };
12611258 defer env_map.deinit();
12621259
1263 color: switch (run.color) {
1264 .manual => {},
1265 .enable => {
1266 try env_map.put("CLICOLOR_FORCE", "1");
1267 env_map.remove("NO_COLOR");
1268 },
1269 .disable => {
1270 try env_map.put("NO_COLOR", "1");
1271 env_map.remove("CLICOLOR_FORCE");
1272 },
1273 .inherit => switch (options.ttyconf) {
1274 .no_color, .windows_api => continue :color .disable,
1275 .escape_codes => continue :color .enable,
1276 },
1277 .auto => {
1278 const capture_stderr = run.captured_stderr != null or switch (run.stdio) {
1279 .check => |checks| checksContainStderr(checks.items),
1280 .infer_from_args, .inherit, .zig_test => false,
1281 };
1282 if (capture_stderr) {
1283 continue :color .disable;
1284 } else {
1285 continue :color .inherit;
1286 }
1287 },
1288 }
1289
12901260 const opt_generic_result = spawnChildAndCollect(run, argv, &env_map, has_side_effects, options, fuzz_context) catch |err| term: {
12911261 // InvalidExe: cpu arch mismatch
12921262 // FileNotFound: can happen with a wrong dynamic linker path
......@@ -1308,7 +1278,7 @@ fn runCommand(
13081278 const need_cross_libc = exe.is_linking_libc and
13091279 (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic));
13101280 const other_target = exe.root_module.resolved_target.?.result;
1311 switch (std.zig.system.getExternalExecutor(&b.graph.host.result, &other_target, .{
1281 switch (std.zig.system.getExternalExecutor(io, &b.graph.host.result, &other_target, .{
13121282 .qemu_fixes_dl = need_cross_libc and b.libc_runtimes_dir != null,
13131283 .link_libc = exe.is_linking_libc,
13141284 })) {
......@@ -1468,8 +1438,8 @@ fn runCommand(
14681438 captured.output.generated_file.path = output_path;
14691439
14701440 const sub_path = b.pathJoin(&output_components);
1471 const sub_path_dirname = fs.path.dirname(sub_path).?;
1472 b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
1441 const sub_path_dirname = Dir.path.dirname(sub_path).?;
1442 b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| {
14731443 return step.fail("unable to make path '{f}{s}': {s}", .{
14741444 b.cache_root, sub_path_dirname, @errorName(err),
14751445 });
......@@ -1480,7 +1450,7 @@ fn runCommand(
14801450 .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace),
14811451 .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace),
14821452 };
1483 b.cache_root.handle.writeFile(.{ .sub_path = sub_path, .data = data }) catch |err| {
1453 b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = data }) catch |err| {
14841454 return step.fail("unable to write file '{f}{s}': {s}", .{
14851455 b.cache_root, sub_path, @errorName(err),
14861456 });
......@@ -1589,6 +1559,8 @@ fn spawnChildAndCollect(
15891559) !?EvalGenericResult {
15901560 const b = run.step.owner;
15911561 const arena = b.allocator;
1562 const graph = b.graph;
1563 const io = graph.io;
15921564
15931565 if (fuzz_context != null) {
15941566 assert(!has_side_effects);
......@@ -1654,8 +1626,12 @@ fn spawnChildAndCollect(
16541626 if (!run.disable_zig_progress and !inherit) {
16551627 child.progress_node = options.progress_node;
16561628 }
1657 if (inherit) std.debug.lockStdErr();
1658 defer if (inherit) std.debug.unlockStdErr();
1629 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {
1630 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
1631 break :m stderr.terminal_mode;
1632 } else .no_color;
1633 defer if (inherit) io.unlockStderr();
1634 try setColorEnvironmentVariables(run, env_map, terminal_mode);
16591635 var timer = try std.time.Timer.start();
16601636 const res = try evalGeneric(run, &child);
16611637 run.step.result_duration_ns = timer.read();
......@@ -1663,6 +1639,35 @@ fn spawnChildAndCollect(
16631639 }
16641640}
16651641
1642fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, terminal_mode: Io.Terminal.Mode) !void {
1643 color: switch (run.color) {
1644 .manual => {},
1645 .enable => {
1646 try env_map.put("CLICOLOR_FORCE", "1");
1647 env_map.remove("NO_COLOR");
1648 },
1649 .disable => {
1650 try env_map.put("NO_COLOR", "1");
1651 env_map.remove("CLICOLOR_FORCE");
1652 },
1653 .inherit => switch (terminal_mode) {
1654 .no_color, .windows_api => continue :color .disable,
1655 .escape_codes => continue :color .enable,
1656 },
1657 .auto => {
1658 const capture_stderr = run.captured_stderr != null or switch (run.stdio) {
1659 .check => |checks| checksContainStderr(checks.items),
1660 .infer_from_args, .inherit, .zig_test => false,
1661 };
1662 if (capture_stderr) {
1663 continue :color .disable;
1664 } else {
1665 continue :color .inherit;
1666 }
1667 },
1668 }
1669}
1670
16661671const StdioPollEnum = enum { stdout, stderr };
16671672
16681673fn evalZigTest(
......@@ -1671,8 +1676,10 @@ fn evalZigTest(
16711676 options: Step.MakeOptions,
16721677 fuzz_context: ?FuzzContext,
16731678) !EvalZigTestResult {
1674 const gpa = run.step.owner.allocator;
1675 const arena = run.step.owner.allocator;
1679 const step_owner = run.step.owner;
1680 const gpa = step_owner.allocator;
1681 const arena = step_owner.allocator;
1682 const io = step_owner.graph.io;
16761683
16771684 // We will update this every time a child runs.
16781685 run.step.result_peak_rss = 0;
......@@ -1691,14 +1698,14 @@ fn evalZigTest(
16911698 };
16921699
16931700 while (true) {
1694 try child.spawn();
1701 try child.spawn(io);
16951702 var poller = std.Io.poll(gpa, StdioPollEnum, .{
16961703 .stdout = child.stdout.?,
16971704 .stderr = child.stderr.?,
16981705 });
16991706 var child_killed = false;
17001707 defer if (!child_killed) {
1701 _ = child.kill() catch {};
1708 _ = child.kill(io) catch {};
17021709 poller.deinit();
17031710 run.step.result_peak_rss = @max(
17041711 run.step.result_peak_rss,
......@@ -1724,11 +1731,11 @@ fn evalZigTest(
17241731 run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered());
17251732
17261733 // Clean up everything and wait for the child to exit.
1727 child.stdin.?.close();
1734 child.stdin.?.close(io);
17281735 child.stdin = null;
17291736 poller.deinit();
17301737 child_killed = true;
1731 const term = try child.wait();
1738 const term = try child.wait(io);
17321739 run.step.result_peak_rss = @max(
17331740 run.step.result_peak_rss,
17341741 child.resource_usage_statistics.getMaxRss() orelse 0,
......@@ -1744,11 +1751,11 @@ fn evalZigTest(
17441751 poller.reader(.stderr).tossBuffered();
17451752
17461753 // Clean up everything and wait for the child to exit.
1747 child.stdin.?.close();
1754 child.stdin.?.close(io);
17481755 child.stdin = null;
17491756 poller.deinit();
17501757 child_killed = true;
1751 const term = try child.wait();
1758 const term = try child.wait(io);
17521759 run.step.result_peak_rss = @max(
17531760 run.step.result_peak_rss,
17541761 child.resource_usage_statistics.getMaxRss() orelse 0,
......@@ -1836,6 +1843,7 @@ fn pollZigTest(
18361843 switch (ctx.fuzz.mode) {
18371844 .forever => {
18381845 sendRunFuzzTestMessage(
1846 io,
18391847 child.stdin.?,
18401848 ctx.unit_test_index,
18411849 .forever,
......@@ -1844,6 +1852,7 @@ fn pollZigTest(
18441852 },
18451853 .limit => |limit| {
18461854 sendRunFuzzTestMessage(
1855 io,
18471856 child.stdin.?,
18481857 ctx.unit_test_index,
18491858 .iterations,
......@@ -1853,11 +1862,11 @@ fn pollZigTest(
18531862 }
18541863 } else if (opt_metadata.*) |*md| {
18551864 // Previous unit test process died or was killed; we're continuing where it left off
1856 requestNextTest(child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
1865 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
18571866 } else {
18581867 // Running unit tests normally
18591868 run.fuzz_tests.clearRetainingCapacity();
1860 sendMessage(child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err };
1869 sendMessage(io, child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err };
18611870 }
18621871
18631872 var active_test_index: ?u32 = null;
......@@ -1973,7 +1982,7 @@ fn pollZigTest(
19731982 active_test_index = null;
19741983 if (timer) |*t| t.reset();
19751984
1976 requestNextTest(child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
1985 requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
19771986 },
19781987 .test_started => {
19791988 active_test_index = opt_metadata.*.?.next_index - 1;
......@@ -2022,7 +2031,7 @@ fn pollZigTest(
20222031 active_test_index = null;
20232032 if (timer) |*t| md.ns_per_test[tr_hdr.index] = t.lap();
20242033
2025 requestNextTest(child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
2034 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
20262035 },
20272036 .coverage_id => {
20282037 coverage_id = body_r.takeInt(u64, .little) catch unreachable;
......@@ -2093,7 +2102,7 @@ pub const CachedTestMetadata = struct {
20932102 }
20942103};
20952104
2096fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
2105fn requestNextTest(io: Io, in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
20972106 while (metadata.next_index < metadata.names.len) {
20982107 const i = metadata.next_index;
20992108 metadata.next_index += 1;
......@@ -2104,31 +2113,31 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr
21042113 if (sub_prog_node.*) |n| n.end();
21052114 sub_prog_node.* = metadata.prog_node.start(name, 0);
21062115
2107 try sendRunTestMessage(in, .run_test, i);
2116 try sendRunTestMessage(io, in, .run_test, i);
21082117 return;
21092118 } else {
21102119 metadata.next_index = std.math.maxInt(u32); // indicate that all tests are done
2111 try sendMessage(in, .exit);
2120 try sendMessage(io, in, .exit);
21122121 }
21132122}
21142123
2115fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
2124fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
21162125 const header: std.zig.Client.Message.Header = .{
21172126 .tag = tag,
21182127 .bytes_len = 0,
21192128 };
2120 var w = file.writer(&.{});
2129 var w = file.writer(io, &.{});
21212130 w.interface.writeStruct(header, .little) catch |err| switch (err) {
21222131 error.WriteFailed => return w.err.?,
21232132 };
21242133}
21252134
2126fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index: u32) !void {
2135fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, index: u32) !void {
21272136 const header: std.zig.Client.Message.Header = .{
21282137 .tag = tag,
21292138 .bytes_len = 4,
21302139 };
2131 var w = file.writer(&.{});
2140 var w = file.writer(io, &.{});
21322141 w.interface.writeStruct(header, .little) catch |err| switch (err) {
21332142 error.WriteFailed => return w.err.?,
21342143 };
......@@ -2138,7 +2147,8 @@ fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index:
21382147}
21392148
21402149fn sendRunFuzzTestMessage(
2141 file: std.fs.File,
2150 io: Io,
2151 file: Io.File,
21422152 index: u32,
21432153 kind: std.Build.abi.fuzz.LimitKind,
21442154 amount_or_instance: u64,
......@@ -2147,7 +2157,7 @@ fn sendRunFuzzTestMessage(
21472157 .tag = .start_fuzzing,
21482158 .bytes_len = 4 + 1 + 8,
21492159 };
2150 var w = file.writer(&.{});
2160 var w = file.writer(io, &.{});
21512161 w.interface.writeStruct(header, .little) catch |err| switch (err) {
21522162 error.WriteFailed => return w.err.?,
21532163 };
......@@ -2167,30 +2177,30 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
21672177 const io = b.graph.io;
21682178 const arena = b.allocator;
21692179
2170 try child.spawn();
2171 errdefer _ = child.kill() catch {};
2180 try child.spawn(io);
2181 errdefer _ = child.kill(io) catch {};
21722182
21732183 try child.waitForSpawn();
21742184
21752185 switch (run.stdin) {
21762186 .bytes => |bytes| {
2177 child.stdin.?.writeAll(bytes) catch |err| {
2178 return run.step.fail("unable to write stdin: {s}", .{@errorName(err)});
2187 child.stdin.?.writeStreamingAll(io, bytes) catch |err| {
2188 return run.step.fail("unable to write stdin: {t}", .{err});
21792189 };
2180 child.stdin.?.close();
2190 child.stdin.?.close(io);
21812191 child.stdin = null;
21822192 },
21832193 .lazy_path => |lazy_path| {
21842194 const path = lazy_path.getPath3(b, &run.step);
2185 const file = path.root_dir.handle.openFile(path.subPathOrDot(), .{}) catch |err| {
2186 return run.step.fail("unable to open stdin file: {s}", .{@errorName(err)});
2195 const file = path.root_dir.handle.openFile(io, path.subPathOrDot(), .{}) catch |err| {
2196 return run.step.fail("unable to open stdin file: {t}", .{err});
21872197 };
2188 defer file.close();
2198 defer file.close(io);
21892199 // TODO https://github.com/ziglang/zig/issues/23955
21902200 var read_buffer: [1024]u8 = undefined;
21912201 var file_reader = file.reader(io, &read_buffer);
21922202 var write_buffer: [1024]u8 = undefined;
2193 var stdin_writer = child.stdin.?.writer(&write_buffer);
2203 var stdin_writer = child.stdin.?.writer(io, &write_buffer);
21942204 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
21952205 error.ReadFailed => return run.step.fail("failed to read from {f}: {t}", .{
21962206 path, file_reader.err.?,
......@@ -2204,7 +2214,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
22042214 stdin_writer.err.?,
22052215 }),
22062216 };
2207 child.stdin.?.close();
2217 child.stdin.?.close(io);
22082218 child.stdin = null;
22092219 },
22102220 .none => {},
......@@ -2263,7 +2273,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
22632273 run.step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0;
22642274
22652275 return .{
2266 .term = try child.wait(),
2276 .term = try child.wait(io),
22672277 .stdout = stdout_bytes,
22682278 .stderr = stderr_bytes,
22692279 };
......@@ -2276,7 +2286,7 @@ fn addPathForDynLibs(run: *Run, artifact: *Step.Compile) void {
22762286 if (compile.root_module.resolved_target.?.result.os.tag == .windows and
22772287 compile.isDynamicLibrary())
22782288 {
2279 addPathDir(run, fs.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?);
2289 addPathDir(run, Dir.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?);
22802290 }
22812291 }
22822292}
lib/std/Build/Step/UpdateSourceFiles.zig+3-3
......@@ -78,13 +78,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
7878 var any_miss = false;
7979 for (usf.output_source_files.items) |output_source_file| {
8080 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
81 b.build_root.handle.makePath(dirname) catch |err| {
81 b.build_root.handle.createDirPath(io, dirname) catch |err| {
8282 return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err });
8383 };
8484 }
8585 switch (output_source_file.contents) {
8686 .bytes => |bytes| {
87 b.build_root.handle.writeFile(.{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {
87 b.build_root.handle.writeFile(io, .{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {
8888 return step.fail("unable to write file '{f}{s}': {t}", .{
8989 b.build_root, output_source_file.sub_path, err,
9090 });
......@@ -99,7 +99,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
9999 .cwd(),
100100 io,
101101 source_path,
102 b.build_root.handle.adaptToNewApi(),
102 b.build_root.handle,
103103 output_source_file.sub_path,
104104 .{},
105105 ) catch |err| {
lib/std/Build/Step/WriteFile.zig+15-22
......@@ -206,9 +206,9 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
206206 }
207207 }
208208
209 const open_dir_cache = try arena.alloc(fs.Dir, write_file.directories.items.len);
209 const open_dir_cache = try arena.alloc(Io.Dir, write_file.directories.items.len);
210210 var open_dirs_count: usize = 0;
211 defer closeDirs(open_dir_cache[0..open_dirs_count]);
211 defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]);
212212
213213 for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| {
214214 man.hash.addBytes(dir.sub_path);
......@@ -218,7 +218,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
218218 const need_derived_inputs = try step.addDirectoryWatchInput(dir.source);
219219 const src_dir_path = dir.source.getPath3(b, step);
220220
221 var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
221 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
222222 return step.fail("unable to open source directory '{f}': {s}", .{
223223 src_dir_path, @errorName(err),
224224 });
......@@ -228,7 +228,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
228228
229229 var it = try src_dir.walk(gpa);
230230 defer it.deinit();
231 while (try it.next()) |entry| {
231 while (try it.next(io)) |entry| {
232232 if (!dir.options.pathIncluded(entry.path)) continue;
233233
234234 switch (entry.kind) {
......@@ -259,16 +259,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
259259
260260 write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest });
261261
262 var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {
263 return step.fail("unable to make path '{f}{s}': {s}", .{
264 b.cache_root, cache_path, @errorName(err),
265 });
266 };
267 defer cache_dir.close();
262 var cache_dir = b.cache_root.handle.createDirPathOpen(io, cache_path, .{}) catch |err|
263 return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, cache_path, err });
264 defer cache_dir.close(io);
268265
269266 for (write_file.files.items) |file| {
270267 if (fs.path.dirname(file.sub_path)) |dirname| {
271 cache_dir.makePath(dirname) catch |err| {
268 cache_dir.createDirPath(io, dirname) catch |err| {
272269 return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{
273270 b.cache_root, cache_path, fs.path.sep, dirname, err,
274271 });
......@@ -276,7 +273,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
276273 }
277274 switch (file.contents) {
278275 .bytes => |bytes| {
279 cache_dir.writeFile(.{ .sub_path = file.sub_path, .data = bytes }) catch |err| {
276 cache_dir.writeFile(io, .{ .sub_path = file.sub_path, .data = bytes }) catch |err| {
280277 return step.fail("unable to write file '{f}{s}{c}{s}': {t}", .{
281278 b.cache_root, cache_path, fs.path.sep, file.sub_path, err,
282279 });
......@@ -284,7 +281,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
284281 },
285282 .copy => |file_source| {
286283 const source_path = file_source.getPath2(b, step);
287 const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir.adaptToNewApi(), file.sub_path, .{}) catch |err| {
284 const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir, file.sub_path, .{}) catch |err| {
288285 return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {t}", .{
289286 source_path, b.cache_root, cache_path, fs.path.sep, file.sub_path, err,
290287 });
......@@ -303,7 +300,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
303300 const dest_dirname = dir.sub_path;
304301
305302 if (dest_dirname.len != 0) {
306 cache_dir.makePath(dest_dirname) catch |err| {
303 cache_dir.createDirPath(io, dest_dirname) catch |err| {
307304 return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{
308305 b.cache_root, cache_path, fs.path.sep, dest_dirname, @errorName(err),
309306 });
......@@ -312,19 +309,19 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
312309
313310 var it = try already_open_dir.walk(gpa);
314311 defer it.deinit();
315 while (try it.next()) |entry| {
312 while (try it.next(io)) |entry| {
316313 if (!dir.options.pathIncluded(entry.path)) continue;
317314
318315 const src_entry_path = try src_dir_path.join(arena, entry.path);
319316 const dest_path = b.pathJoin(&.{ dest_dirname, entry.path });
320317 switch (entry.kind) {
321 .directory => try cache_dir.makePath(dest_path),
318 .directory => try cache_dir.createDirPath(io, dest_path),
322319 .file => {
323320 const prev_status = Io.Dir.updateFile(
324 src_entry_path.root_dir.handle.adaptToNewApi(),
321 src_entry_path.root_dir.handle,
325322 io,
326323 src_entry_path.sub_path,
327 cache_dir.adaptToNewApi(),
324 cache_dir,
328325 dest_path,
329326 .{},
330327 ) catch |err| {
......@@ -341,7 +338,3 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
341338
342339 try step.writeManifest(&man);
343340}
344
345fn closeDirs(dirs: []fs.Dir) void {
346 for (dirs) |*d| d.close();
347}
lib/std/Build/Watch.zig+10-10
......@@ -122,7 +122,7 @@ const Os = switch (builtin.os.tag) {
122122 }) catch return error.NameTooLong;
123123 const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer);
124124 stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle);
125 try posix.name_to_handle_at(path.root_dir.handle.fd, adjusted_path, stack_ptr, mount_id, std.os.linux.AT.HANDLE_FID);
125 try posix.name_to_handle_at(path.root_dir.handle.handle, adjusted_path, stack_ptr, mount_id, std.os.linux.AT.HANDLE_FID);
126126 const stack_lfh: FileHandle = .{ .handle = stack_ptr };
127127 return stack_lfh.clone(gpa);
128128 }
......@@ -222,7 +222,7 @@ const Os = switch (builtin.os.tag) {
222222 posix.fanotify_mark(fan_fd, .{
223223 .ADD = true,
224224 .ONLYDIR = true,
225 }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| {
225 }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| {
226226 fatal("unable to watch {f}: {s}", .{ path, @errorName(err) });
227227 };
228228 }
......@@ -275,7 +275,7 @@ const Os = switch (builtin.os.tag) {
275275 posix.fanotify_mark(fan_fd, .{
276276 .REMOVE = true,
277277 .ONLYDIR = true,
278 }, fan_mask, path.root_dir.handle.fd, path.subPathOrDot()) catch |err| switch (err) {
278 }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| switch (err) {
279279 error.FileNotFound => {}, // Expected, harmless.
280280 else => |e| std.log.warn("unable to unwatch '{f}': {s}", .{ path, @errorName(e) }),
281281 };
......@@ -350,10 +350,10 @@ const Os = switch (builtin.os.tag) {
350350 }
351351
352352 fn init(gpa: Allocator, path: Cache.Path) !*@This() {
353 // The following code is a drawn out NtCreateFile call. (mostly adapted from std.fs.Dir.makeOpenDirAccessMaskW)
353 // The following code is a drawn out NtCreateFile call. (mostly adapted from Io.Dir.makeOpenDirAccessMaskW)
354354 // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW.
355355 var dir_handle: windows.HANDLE = undefined;
356 const root_fd = path.root_dir.handle.fd;
356 const root_fd = path.root_dir.handle.handle;
357357 const sub_path = path.subPathOrDot();
358358 const sub_path_w = try windows.sliceToPrefixedFileW(root_fd, sub_path);
359359 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
......@@ -681,10 +681,10 @@ const Os = switch (builtin.os.tag) {
681681 if (!gop.found_existing) {
682682 const skip_open_dir = path.sub_path.len == 0;
683683 const dir_fd = if (skip_open_dir)
684 path.root_dir.handle.fd
684 path.root_dir.handle.handle
685685 else
686 posix.openat(path.root_dir.handle.fd, path.sub_path, dir_open_flags, 0) catch |err| {
687 fatal("failed to open directory {f}: {s}", .{ path, @errorName(err) });
686 posix.openat(path.root_dir.handle.handle, path.sub_path, dir_open_flags, 0) catch |err| {
687 fatal("failed to open directory {f}: {t}", .{ path, err });
688688 };
689689 // Empirically the dir has to stay open or else no events are triggered.
690690 errdefer if (!skip_open_dir) posix.close(dir_fd);
......@@ -750,7 +750,7 @@ const Os = switch (builtin.os.tag) {
750750 // to access that data via the dir_fd field.
751751 const path = w.dir_table.keys()[i];
752752 const dir_fd = if (path.sub_path.len == 0)
753 path.root_dir.handle.fd
753 path.root_dir.handle.handle
754754 else
755755 handles.items(.dir_fd)[i];
756756 assert(dir_fd != -1);
......@@ -761,7 +761,7 @@ const Os = switch (builtin.os.tag) {
761761 const last_dir_fd = fd: {
762762 const last_path = w.dir_table.keys()[handles.len - 1];
763763 const last_dir_fd = if (last_path.sub_path.len == 0)
764 last_path.root_dir.handle.fd
764 last_path.root_dir.handle.handle
765765 else
766766 handles.items(.dir_fd)[handles.len - 1];
767767 assert(last_dir_fd != -1);
lib/std/Build/Watch/FsEvents.zig+3-2
......@@ -102,10 +102,10 @@ pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {
102102 };
103103}
104104
105pub fn deinit(fse: *FsEvents, gpa: Allocator) void {
105pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {
106106 dispatch_release(fse.waiting_semaphore);
107107 dispatch_release(fse.dispatch_queue);
108 fse.core_services.close();
108 fse.core_services.close(io);
109109
110110 gpa.free(fse.watch_roots);
111111 fse.watch_paths.deinit(gpa);
......@@ -487,6 +487,7 @@ const FSEventStreamEventFlags = packed struct(u32) {
487487};
488488
489489const std = @import("std");
490const Io = std.Io;
490491const assert = std.debug.assert;
491492const Allocator = std.mem.Allocator;
492493const watch_log = std.log.scoped(.watch);
lib/std/Build/WebServer.zig+17-19
......@@ -2,7 +2,6 @@ gpa: Allocator,
22graph: *const Build.Graph,
33all_steps: []const *Build.Step,
44listen_address: net.IpAddress,
5ttyconf: Io.tty.Config,
65root_prog_node: std.Progress.Node,
76watch: bool,
87
......@@ -52,7 +51,6 @@ pub fn notifyUpdate(ws: *WebServer) void {
5251
5352pub const Options = struct {
5453 gpa: Allocator,
55 ttyconf: Io.tty.Config,
5654 graph: *const std.Build.Graph,
5755 all_steps: []const *Build.Step,
5856 root_prog_node: std.Progress.Node,
......@@ -98,7 +96,6 @@ pub fn init(opts: Options) WebServer {
9896
9997 return .{
10098 .gpa = opts.gpa,
101 .ttyconf = opts.ttyconf,
10299 .graph = opts.graph,
103100 .all_steps = all_steps,
104101 .listen_address = opts.listen_address,
......@@ -129,6 +126,7 @@ pub fn init(opts: Options) WebServer {
129126}
130127pub fn deinit(ws: *WebServer) void {
131128 const gpa = ws.gpa;
129 const io = ws.graph.io;
132130
133131 gpa.free(ws.step_names_trailing);
134132 gpa.free(ws.step_status_bits);
......@@ -139,7 +137,7 @@ pub fn deinit(ws: *WebServer) void {
139137 gpa.free(ws.time_report_update_times);
140138
141139 if (ws.serve_thread) |t| {
142 if (ws.tcp_server) |*s| s.stream.close();
140 if (ws.tcp_server) |*s| s.stream.close(io);
143141 t.join();
144142 }
145143 if (ws.tcp_server) |*s| s.deinit();
......@@ -217,9 +215,9 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
217215 else => {},
218216 }
219217 if (@bitSizeOf(usize) != 64) {
220 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,
221 // being compatible with `std.fs.getEndPos() u64`'s return value. This is not the case
222 // on 32-bit platforms.
218 // Current implementation depends on posix.mmap()'s second
219 // parameter, `length: usize`, being compatible with file system's
220 // u64 return value. This is not the case on 32-bit platforms.
223221 // Affects or affected by issues #5185, #22523, and #22464.
224222 std.process.fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
225223 }
......@@ -232,7 +230,6 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
232230 ws.fuzz = Fuzz.init(
233231 ws.gpa,
234232 ws.graph.io,
235 ws.ttyconf,
236233 ws.all_steps,
237234 ws.root_prog_node,
238235 .{ .forever = .{ .ws = ws } },
......@@ -468,11 +465,12 @@ pub fn serveFile(
468465 content_type: []const u8,
469466) !void {
470467 const gpa = ws.gpa;
468 const io = ws.graph.io;
471469 // The desired API is actually sendfile, which will require enhancing http.Server.
472470 // We load the file with every request so that the user can make changes to the file
473471 // and refresh the HTML page without restarting this server.
474 const file_contents = path.root_dir.handle.readFileAlloc(path.sub_path, gpa, .limited(10 * 1024 * 1024)) catch |err| {
475 log.err("failed to read '{f}': {s}", .{ path, @errorName(err) });
472 const file_contents = path.root_dir.handle.readFileAlloc(io, path.sub_path, gpa, .limited(10 * 1024 * 1024)) catch |err| {
473 log.err("failed to read '{f}': {t}", .{ path, err });
476474 return error.AlreadyReported;
477475 };
478476 defer gpa.free(file_contents);
......@@ -503,14 +501,14 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
503501 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
504502
505503 for (paths) |path| {
506 var file = path.root_dir.handle.openFile(path.sub_path, .{}) catch |err| {
504 var file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| {
507505 log.err("failed to open '{f}': {s}", .{ path, @errorName(err) });
508506 continue;
509507 };
510 defer file.close();
511 const stat = try file.stat();
508 defer file.close(io);
509 const stat = try file.stat(io);
512510 var read_buffer: [1024]u8 = undefined;
513 var file_reader: Io.File.Reader = .initSize(file.adaptToNewApi(), io, &read_buffer, stat.size);
511 var file_reader: Io.File.Reader = .initSize(file, io, &read_buffer, stat.size);
514512
515513 // TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can
516514 // be cwd-relative. This is also related to why linkification doesn't work in the fuzzer UI:
......@@ -578,7 +576,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
578576 child.stdin_behavior = .Pipe;
579577 child.stdout_behavior = .Pipe;
580578 child.stderr_behavior = .Pipe;
581 try child.spawn();
579 try child.spawn(io);
582580
583581 var poller = Io.poll(gpa, enum { stdout, stderr }, .{
584582 .stdout = child.stdout.?,
......@@ -586,7 +584,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
586584 });
587585 defer poller.deinit();
588586
589 try child.stdin.?.writeAll(@ptrCast(@as([]const std.zig.Client.Message.Header, &.{
587 try child.stdin.?.writeStreamingAll(io, @ptrCast(@as([]const std.zig.Client.Message.Header, &.{
590588 .{ .tag = .update, .bytes_len = 0 },
591589 .{ .tag = .exit, .bytes_len = 0 },
592590 })));
......@@ -634,10 +632,10 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
634632 }
635633
636634 // Send EOF to stdin.
637 child.stdin.?.close();
635 child.stdin.?.close(io);
638636 child.stdin = null;
639637
640 switch (try child.wait()) {
638 switch (try child.wait(io)) {
641639 .Exited => |code| {
642640 if (code != 0) {
643641 log.err(
......@@ -657,7 +655,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
657655 }
658656
659657 if (result_error_bundle.errorMessageCount() > 0) {
660 result_error_bundle.renderToStdErr(.{}, .auto);
658 try result_error_bundle.renderToStderr(io, .{}, .auto);
661659 log.err("the following command failed with {d} compilation errors:\n{s}", .{
662660 result_error_bundle.errorMessageCount(),
663661 try Build.Step.allocPrintCmd(arena, null, argv.items),
lib/std/Io.zig+104-28
......@@ -82,8 +82,6 @@ pub const Limit = enum(usize) {
8282pub const Reader = @import("Io/Reader.zig");
8383pub const Writer = @import("Io/Writer.zig");
8484
85pub const tty = @import("Io/tty.zig");
86
8785pub fn poll(
8886 gpa: Allocator,
8987 comptime StreamEnum: type,
......@@ -528,14 +526,13 @@ pub fn Poller(comptime StreamEnum: type) type {
528526/// Given an enum, returns a struct with fields of that enum, each field
529527/// representing an I/O stream for polling.
530528pub fn PollFiles(comptime StreamEnum: type) type {
531 return @Struct(.auto, null, std.meta.fieldNames(StreamEnum), &@splat(std.fs.File), &@splat(.{}));
529 return @Struct(.auto, null, std.meta.fieldNames(StreamEnum), &@splat(Io.File), &@splat(.{}));
532530}
533531
534532test {
535533 _ = net;
536534 _ = Reader;
537535 _ = Writer;
538 _ = tty;
539536 _ = Evented;
540537 _ = Threaded;
541538 _ = @import("Io/test.zig");
......@@ -662,27 +659,66 @@ pub const VTable = struct {
662659 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
663660 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,
664661
665 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,
666 dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,
667 dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
662 dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void,
663 dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus,
664 dirCreateDirPathOpen: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions, Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir,
665 dirOpenDir: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.OpenError!Dir,
668666 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
669 dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
670 dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void,
671 dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
672 dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
673 dirOpenDir: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.OpenError!Dir,
674 dirClose: *const fn (?*anyopaque, Dir) void,
667 dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat,
668 dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void,
669 dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File,
670 dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File,
671 dirClose: *const fn (?*anyopaque, []const Dir) void,
672 dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize,
673 dirRealPath: *const fn (?*anyopaque, Dir, out_buffer: []u8) Dir.RealPathError!usize,
674 dirRealPathFile: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize,
675 dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void,
676 dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void,
677 dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,
678 dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void,
679 dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,
680 dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,
681 dirSetFileOwner: *const fn (?*anyopaque, Dir, []const u8, ?File.Uid, ?File.Gid, Dir.SetFileOwnerOptions) Dir.SetFileOwnerError!void,
682 dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void,
683 dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void,
684 dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
685 dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
686 dirHardLink: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8, Dir.HardLinkOptions) Dir.HardLinkError!void,
687
675688 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
676 fileClose: *const fn (?*anyopaque, File) void,
677 fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize,
678 fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize,
689 fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,
690 fileClose: *const fn (?*anyopaque, []const File) void,
691 fileWriteStreaming: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize) File.Writer.Error!usize,
692 fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize,
693 fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize,
694 fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize,
679695 /// Returns 0 on end of stream.
680 fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize,
696 fileReadStreaming: *const fn (?*anyopaque, File, data: []const []u8) File.Reader.Error!usize,
681697 /// Returns 0 on end of stream.
682 fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
698 fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize,
683699 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
684700 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,
685 openSelfExe: *const fn (?*anyopaque, File.OpenFlags) File.OpenSelfExeError!File,
701 fileSync: *const fn (?*anyopaque, File) File.SyncError!void,
702 fileIsTty: *const fn (?*anyopaque, File) Cancelable!bool,
703 fileEnableAnsiEscapeCodes: *const fn (?*anyopaque, File) File.EnableAnsiEscapeCodesError!void,
704 fileSupportsAnsiEscapeCodes: *const fn (?*anyopaque, File) Cancelable!bool,
705 fileSetLength: *const fn (?*anyopaque, File, u64) File.SetLengthError!void,
706 fileSetOwner: *const fn (?*anyopaque, File, ?File.Uid, ?File.Gid) File.SetOwnerError!void,
707 fileSetPermissions: *const fn (?*anyopaque, File, File.Permissions) File.SetPermissionsError!void,
708 fileSetTimestamps: *const fn (?*anyopaque, File, last_accessed: Timestamp, last_modified: Timestamp) File.SetTimestampsError!void,
709 fileSetTimestampsNow: *const fn (?*anyopaque, File) File.SetTimestampsError!void,
710 fileLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!void,
711 fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool,
712 fileUnlock: *const fn (?*anyopaque, File) void,
713 fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void,
714 fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize,
715
716 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
717 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
718 lockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!LockedStderr,
719 tryLockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!?LockedStderr,
720 unlockStderr: *const fn (?*anyopaque) void,
721 processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void,
686722
687723 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
688724 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
......@@ -698,7 +734,8 @@ pub const VTable = struct {
698734 /// Returns 0 on end of stream.
699735 netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize,
700736 netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
701 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
737 netWriteFile: *const fn (?*anyopaque, net.Socket.Handle, header: []const u8, *Io.File.Reader, Io.Limit) net.Stream.Writer.WriteFileError!usize,
738 netClose: *const fn (?*anyopaque, handle: []const net.Socket.Handle) void,
702739 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
703740 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
704741 netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void,
......@@ -723,6 +760,7 @@ pub const UnexpectedError = error{
723760
724761pub const Dir = @import("Io/Dir.zig");
725762pub const File = @import("Io/File.zig");
763pub const Terminal = @import("Io/Terminal.zig");
726764
727765pub const Clock = enum {
728766 /// A settable system-wide clock that measures real (i.e. wall-clock)
......@@ -1277,17 +1315,21 @@ pub fn futexWait(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, e
12771315/// wakeups are possible. It remains the caller's responsibility to differentiate between these
12781316/// three possible wake-up reasons if necessary.
12791317pub fn futexWaitTimeout(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, expected: T, timeout: Timeout) Cancelable!void {
1280 comptime assert(@sizeOf(T) == 4);
1281 const expected_raw: *align(1) const u32 = @ptrCast(&expected);
1282 return io.vtable.futexWait(io.userdata, @ptrCast(ptr), expected_raw.*, timeout);
1318 const expected_int: u32 = switch (@typeInfo(T)) {
1319 .@"enum" => @bitCast(@intFromEnum(expected)),
1320 else => @bitCast(expected),
1321 };
1322 return io.vtable.futexWait(io.userdata, @ptrCast(ptr), expected_int, timeout);
12831323}
12841324/// Same as `futexWait`, except does not introduce a cancelation point.
12851325///
12861326/// For a description of cancelation and cancelation points, see `Future.cancel`.
12871327pub fn futexWaitUncancelable(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, expected: T) void {
1288 comptime assert(@sizeOf(T) == @sizeOf(u32));
1289 const expected_raw: *align(1) const u32 = @ptrCast(&expected);
1290 io.vtable.futexWaitUncancelable(io.userdata, @ptrCast(ptr), expected_raw.*);
1328 const expected_int: u32 = switch (@typeInfo(T)) {
1329 .@"enum" => @bitCast(@intFromEnum(expected)),
1330 else => @bitCast(expected),
1331 };
1332 io.vtable.futexWaitUncancelable(io.userdata, @ptrCast(ptr), expected_int);
12911333}
12921334/// Unblocks pending futex waits on `ptr`, up to a limit of `max_waiters` calls.
12931335pub fn futexWake(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, max_waiters: u32) void {
......@@ -1539,10 +1581,12 @@ pub const Event = enum(u32) {
15391581 }
15401582 }
15411583
1584 pub const WaitTimeoutError = error{Timeout} || Cancelable;
1585
15421586 /// Blocks the calling thread until either the logical boolean is set, the timeout expires, or a
15431587 /// spurious wakeup occurs. If the timeout expires or a spurious wakeup occurs, `error.Timeout`
15441588 /// is returned.
1545 pub fn waitTimeout(event: *Event, io: Io, timeout: Timeout) (error{Timeout} || Cancelable)!void {
1589 pub fn waitTimeout(event: *Event, io: Io, timeout: Timeout) WaitTimeoutError!void {
15461590 if (@cmpxchgStrong(Event, event, .unset, .waiting, .acquire, .acquire)) |prev| switch (prev) {
15471591 .unset => unreachable,
15481592 .waiting => assert(!builtin.single_threaded), // invalid state
......@@ -1555,7 +1599,7 @@ pub const Event = enum(u32) {
15551599 // waiters would wake up when a *new waiter* was added. So it's easiest to just leave
15561600 // the state at `.waiting`---at worst it causes one redundant call to `futexWake`.
15571601 }
1558 io.futexWaitTimeout(Event, event, .waiting, timeout);
1602 try io.futexWaitTimeout(Event, event, .waiting, timeout);
15591603 switch (@atomicLoad(Event, event, .acquire)) {
15601604 .unset => unreachable, // `reset` called before pending `wait` returned
15611605 .waiting => return error.Timeout,
......@@ -2136,3 +2180,35 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
21362180 else => unreachable,
21372181 }
21382182}
2183
2184pub const LockedStderr = struct {
2185 file_writer: *File.Writer,
2186 terminal_mode: Terminal.Mode,
2187
2188 pub fn terminal(ls: LockedStderr) Terminal {
2189 return .{
2190 .writer = &ls.file_writer.interface,
2191 .mode = ls.terminal_mode,
2192 };
2193 }
2194};
2195
2196/// For doing application-level writes to the standard error stream.
2197/// Coordinates also with debug-level writes that are ignorant of Io interface
2198/// and implementations. When this returns, `std.process.stderr_thread_mutex`
2199/// will be locked.
2200///
2201/// See also:
2202/// * `tryLockStderr`
2203pub fn lockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!LockedStderr {
2204 return io.vtable.lockStderr(io.userdata, buffer, terminal_mode);
2205}
2206
2207/// Same as `lockStderr` but non-blocking.
2208pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!?LockedStderr {
2209 return io.vtable.tryLockStderr(io.userdata, buffer, terminal_mode);
2210}
2211
2212pub fn unlockStderr(io: Io) void {
2213 return io.vtable.unlockStderr(io.userdata);
2214}
lib/std/Io/Dir.zig+1526-67
......@@ -1,4 +1,5 @@
11const Dir = @This();
2const root = @import("root");
23
34const builtin = @import("builtin");
45const native_os = builtin.os.tag;
......@@ -6,11 +7,71 @@ const native_os = builtin.os.tag;
67const std = @import("../std.zig");
78const Io = std.Io;
89const File = Io.File;
10const assert = std.debug.assert;
11const Allocator = std.mem.Allocator;
912
1013handle: Handle,
1114
12pub const Mode = Io.File.Mode;
13pub const default_mode: Mode = 0o755;
15pub const path = std.fs.path;
16
17/// The maximum length of a file path that the operating system will accept.
18///
19/// Paths, including those returned from file system operations, may be longer
20/// than this length, but such paths cannot be successfully passed back in
21/// other file system operations. However, all path components returned by file
22/// system operations are assumed to fit into a `u8` array of this length.
23///
24/// The byte count includes room for a null sentinel byte.
25///
26/// * On Windows, `[]u8` file paths are encoded as
27/// [WTF-8](https://wtf-8.codeberg.page/).
28/// * On WASI, `[]u8` file paths are encoded as valid UTF-8.
29/// * On other platforms, `[]u8` file paths are opaque sequences of bytes with
30/// no particular encoding.
31pub const max_path_bytes = switch (native_os) {
32 .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .illumos, .plan9, .emscripten, .wasi, .serenity => std.posix.PATH_MAX,
33 // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes.
34 // If it would require 4 WTF-8 bytes, then there would be a surrogate
35 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
36 // +1 for the null byte at the end, which can be encoded in 1 byte.
37 .windows => std.os.windows.PATH_MAX_WIDE * 3 + 1,
38 else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX"))
39 root.os.PATH_MAX
40 else
41 @compileError("PATH_MAX not implemented for " ++ @tagName(native_os)),
42};
43
44/// This represents the maximum size of a `[]u8` file name component that
45/// the platform's common file systems support. File name components returned by file system
46/// operations are likely to fit into a `u8` array of this length, but
47/// (depending on the platform) this assumption may not hold for every configuration.
48/// The byte count does not include a null sentinel byte.
49/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://wtf-8.codeberg.page/).
50/// On WASI, file name components are encoded as valid UTF-8.
51/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding.
52pub const max_name_bytes = switch (native_os) {
53 .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .illumos, .serenity => std.posix.NAME_MAX,
54 // Haiku's NAME_MAX includes the null terminator, so subtract one.
55 .haiku => std.posix.NAME_MAX - 1,
56 // Each WTF-16LE character may be expanded to 3 WTF-8 bytes.
57 // If it would require 4 WTF-8 bytes, then there would be a surrogate
58 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
59 .windows => std.os.windows.NAME_MAX * 3,
60 // For WASI, the MAX_NAME will depend on the host OS, so it needs to be
61 // as large as the largest max_name_bytes (Windows) in order to work on any host OS.
62 // TODO determine if this is a reasonable approach
63 .wasi => std.os.windows.NAME_MAX * 3,
64 else => if (@hasDecl(root, "os") and @hasDecl(root.os, "NAME_MAX"))
65 root.os.NAME_MAX
66 else
67 @compileError("NAME_MAX not implemented for " ++ @tagName(native_os)),
68};
69
70pub const Entry = struct {
71 name: []const u8,
72 kind: File.Kind,
73 inode: File.INode,
74};
1475
1576/// Returns a handle to the current working directory.
1677///
......@@ -20,6 +81,8 @@ pub const default_mode: Mode = 0o755;
2081/// Closing the returned `Dir` is checked illegal behavior.
2182///
2283/// On POSIX targets, this function is comptime-callable.
84///
85/// On WASI, the value this returns is application-configurable.
2386pub fn cwd() Dir {
2487 return switch (native_os) {
2588 .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },
......@@ -28,9 +91,314 @@ pub fn cwd() Dir {
2891 };
2992}
3093
94pub const Reader = struct {
95 dir: Dir,
96 state: State,
97 /// Stores I/O implementation specific data.
98 buffer: []align(@alignOf(usize)) u8,
99 /// Index of next entry in `buffer`.
100 index: usize,
101 /// Fill position of `buffer`.
102 end: usize,
103
104 /// A length for `buffer` that allows all implementations to function.
105 pub const min_buffer_len = switch (native_os) {
106 .linux => std.mem.alignForward(usize, @sizeOf(std.os.linux.dirent64), 8) +
107 std.mem.alignForward(usize, max_name_bytes, 8),
108 .windows => len: {
109 const max_info_len = @sizeOf(std.os.windows.FILE_BOTH_DIR_INFORMATION) + std.os.windows.NAME_MAX * 2;
110 const info_align = @alignOf(std.os.windows.FILE_BOTH_DIR_INFORMATION);
111 const reserved_len = std.mem.alignForward(usize, max_name_bytes, info_align) - max_info_len;
112 break :len std.mem.alignForward(usize, reserved_len, info_align) + max_info_len;
113 },
114 .wasi => @sizeOf(std.os.wasi.dirent_t) +
115 std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.wasi.dirent_t)),
116 else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)),
117 };
118
119 pub const State = enum {
120 /// Indicates the next call to `read` should rewind and start over the
121 /// directory listing.
122 reset,
123 reading,
124 finished,
125 };
126
127 pub const Error = error{
128 AccessDenied,
129 PermissionDenied,
130 SystemResources,
131 } || Io.UnexpectedError || Io.Cancelable;
132
133 /// Asserts that `buffer` has length at least `min_buffer_len`.
134 pub fn init(dir: Dir, buffer: []align(@alignOf(usize)) u8) Reader {
135 assert(buffer.len >= min_buffer_len);
136 return .{
137 .dir = dir,
138 .state = .reset,
139 .index = 0,
140 .end = 0,
141 .buffer = buffer,
142 };
143 }
144
145 /// All `Entry.name` are invalidated with the next call to `read` or
146 /// `next`.
147 pub fn read(r: *Reader, io: Io, buffer: []Entry) Error!usize {
148 return io.vtable.dirRead(io.userdata, r, buffer);
149 }
150
151 /// `Entry.name` is invalidated with the next call to `read` or `next`.
152 pub fn next(r: *Reader, io: Io) Error!?Entry {
153 var buffer: [1]Entry = undefined;
154 while (true) {
155 const n = try read(r, io, &buffer);
156 if (n == 1) return buffer[0];
157 if (r.state == .finished) return null;
158 }
159 }
160
161 pub fn reset(r: *Reader) void {
162 r.state = .reset;
163 r.index = 0;
164 r.end = 0;
165 }
166};
167
168/// This API is designed for convenience rather than performance:
169/// * It chooses a buffer size rather than allowing the user to provide one.
170/// * It is movable by only requesting one `Entry` at a time from the `Io`
171/// implementation rather than doing batch operations.
172///
173/// Still, it will do a decent job of minimizing syscall overhead. For a
174/// lower level abstraction, see `Reader`. For a higher level abstraction,
175/// see `Walker`.
176pub const Iterator = struct {
177 reader: Reader,
178 reader_buffer: [reader_buffer_len]u8 align(@alignOf(usize)),
179
180 pub const reader_buffer_len = 2048;
181
182 comptime {
183 assert(reader_buffer_len >= Reader.min_buffer_len);
184 }
185
186 pub const Error = Reader.Error;
187
188 pub fn init(dir: Dir, reader_state: Reader.State) Iterator {
189 return .{
190 .reader = .{
191 .dir = dir,
192 .state = reader_state,
193 .index = 0,
194 .end = 0,
195 .buffer = undefined,
196 },
197 .reader_buffer = undefined,
198 };
199 }
200
201 pub fn next(it: *Iterator, io: Io) Error!?Entry {
202 it.reader.buffer = &it.reader_buffer;
203 return it.reader.next(io);
204 }
205};
206
207pub fn iterate(dir: Dir) Iterator {
208 return .init(dir, .reset);
209}
210
211/// Like `iterate`, but will not reset the directory cursor before the first
212/// iteration. This should only be used in cases where it is known that the
213/// `Dir` has not had its cursor modified yet (e.g. it was just opened).
214pub fn iterateAssumeFirstIteration(dir: Dir) Iterator {
215 return .init(dir, .reading);
216}
217
218pub const SelectiveWalker = struct {
219 stack: std.ArrayList(StackItem),
220 name_buffer: std.ArrayList(u8),
221 allocator: Allocator,
222
223 pub const Error = Iterator.Error || Allocator.Error;
224
225 const StackItem = struct {
226 iter: Iterator,
227 dirname_len: usize,
228 };
229
230 /// After each call to this function, and on deinit(), the memory returned
231 /// from this function becomes invalid. A copy must be made in order to keep
232 /// a reference to the path.
233 pub fn next(self: *SelectiveWalker, io: Io) Error!?Walker.Entry {
234 while (self.stack.items.len > 0) {
235 const top = &self.stack.items[self.stack.items.len - 1];
236 var dirname_len = top.dirname_len;
237 if (top.iter.next(io) catch |err| {
238 // If we get an error, then we want the user to be able to continue
239 // walking if they want, which means that we need to pop the directory
240 // that errored from the stack. Otherwise, all future `next` calls would
241 // likely just fail with the same error.
242 var item = self.stack.pop().?;
243 if (self.stack.items.len != 0) {
244 item.iter.reader.dir.close(io);
245 }
246 return err;
247 }) |entry| {
248 self.name_buffer.shrinkRetainingCapacity(dirname_len);
249 if (self.name_buffer.items.len != 0) {
250 try self.name_buffer.append(self.allocator, path.sep);
251 dirname_len += 1;
252 }
253 try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1);
254 self.name_buffer.appendSliceAssumeCapacity(entry.name);
255 self.name_buffer.appendAssumeCapacity(0);
256 const walker_entry: Walker.Entry = .{
257 .dir = top.iter.reader.dir,
258 .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0],
259 .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0],
260 .kind = entry.kind,
261 };
262 return walker_entry;
263 } else {
264 var item = self.stack.pop().?;
265 if (self.stack.items.len != 0) {
266 item.iter.reader.dir.close(io);
267 }
268 }
269 }
270 return null;
271 }
272
273 /// Traverses into the directory, continuing walking one level down.
274 pub fn enter(self: *SelectiveWalker, io: Io, entry: Walker.Entry) !void {
275 if (entry.kind != .directory) {
276 @branchHint(.cold);
277 return;
278 }
279
280 var new_dir = entry.dir.openDir(io, entry.basename, .{ .iterate = true }) catch |err| {
281 switch (err) {
282 error.NameTooLong => unreachable,
283 else => |e| return e,
284 }
285 };
286 errdefer new_dir.close(io);
287
288 try self.stack.append(self.allocator, .{
289 .iter = new_dir.iterateAssumeFirstIteration(),
290 .dirname_len = self.name_buffer.items.len - 1,
291 });
292 }
293
294 pub fn deinit(self: *SelectiveWalker) void {
295 self.name_buffer.deinit(self.allocator);
296 self.stack.deinit(self.allocator);
297 }
298
299 /// Leaves the current directory, continuing walking one level up.
300 /// If the current entry is a directory entry, then the "current directory"
301 /// will pertain to that entry if `enter` is called before `leave`.
302 pub fn leave(self: *SelectiveWalker, io: Io) void {
303 var item = self.stack.pop().?;
304 if (self.stack.items.len != 0) {
305 @branchHint(.likely);
306 item.iter.reader.dir.close(io);
307 }
308 }
309};
310
311/// Recursively iterates over a directory, but requires the user to
312/// opt-in to recursing into each directory entry.
313///
314/// `dir` must have been opened with `OpenOptions.iterate` set to `true`.
315///
316/// `Walker.deinit` releases allocated memory and directory handles.
317///
318/// The order of returned file system entries is undefined.
319///
320/// `dir` will not be closed after walking it.
321///
322/// See also `walk`.
323pub fn walkSelectively(dir: Dir, allocator: Allocator) !SelectiveWalker {
324 var stack: std.ArrayList(SelectiveWalker.StackItem) = .empty;
325
326 try stack.append(allocator, .{
327 .iter = dir.iterate(),
328 .dirname_len = 0,
329 });
330
331 return .{
332 .stack = stack,
333 .name_buffer = .{},
334 .allocator = allocator,
335 };
336}
337
338pub const Walker = struct {
339 inner: SelectiveWalker,
340
341 pub const Entry = struct {
342 /// The containing directory. This can be used to operate directly on `basename`
343 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
344 /// The directory remains open until `next` or `deinit` is called.
345 dir: Dir,
346 basename: [:0]const u8,
347 path: [:0]const u8,
348 kind: File.Kind,
349
350 /// Returns the depth of the entry relative to the initial directory.
351 /// Returns 1 for a direct child of the initial directory, 2 for an entry
352 /// within a direct child of the initial directory, etc.
353 pub fn depth(self: Walker.Entry) usize {
354 return std.mem.countScalar(u8, self.path, path.sep) + 1;
355 }
356 };
357
358 /// After each call to this function, and on deinit(), the memory returned
359 /// from this function becomes invalid. A copy must be made in order to keep
360 /// a reference to the path.
361 pub fn next(self: *Walker, io: Io) !?Walker.Entry {
362 const entry = try self.inner.next(io);
363 if (entry != null and entry.?.kind == .directory) {
364 try self.inner.enter(io, entry.?);
365 }
366 return entry;
367 }
368
369 pub fn deinit(self: *Walker) void {
370 self.inner.deinit();
371 }
372
373 /// Leaves the current directory, continuing walking one level up.
374 /// If the current entry is a directory entry, then the "current directory"
375 /// is the directory pertaining to the current entry.
376 pub fn leave(self: *Walker) void {
377 self.inner.leave();
378 }
379};
380
381/// Recursively iterates over a directory.
382///
383/// `dir` must have been opened with `OpenOptions.iterate` set to `true`.
384///
385/// `Walker.deinit` releases allocated memory and directory handles.
386///
387/// The order of returned file system entries is undefined.
388///
389/// `dir` will not be closed after walking it.
390///
391/// See also:
392/// * `walkSelectively`
393pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker {
394 return .{ .inner = try walkSelectively(dir, allocator) };
395}
396
31397pub const Handle = std.posix.fd_t;
32398
33399pub const PathNameError = error{
400 /// Returned when an insufficient buffer is provided that cannot fit the
401 /// path name.
34402 NameTooLong,
35403 /// File system cannot encode the requested file name bytes.
36404 /// Could be due to invalid WTF-8 on Windows, invalid UTF-8 on WASI,
......@@ -69,6 +437,11 @@ pub fn access(dir: Dir, io: Io, sub_path: []const u8, options: AccessOptions) Ac
69437 return io.vtable.dirAccess(io.userdata, dir, sub_path, options);
70438}
71439
440pub fn accessAbsolute(io: Io, absolute_path: []const u8, options: AccessOptions) AccessError!void {
441 assert(path.isAbsolute(absolute_path));
442 return access(.cwd(), io, absolute_path, options);
443}
444
72445pub const OpenError = error{
73446 FileNotFound,
74447 NotDir,
......@@ -108,8 +481,17 @@ pub fn openDir(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) Ope
108481 return io.vtable.dirOpenDir(io.userdata, dir, sub_path, options);
109482}
110483
484pub fn openDirAbsolute(io: Io, absolute_path: []const u8, options: OpenOptions) OpenError!Dir {
485 assert(path.isAbsolute(absolute_path));
486 return openDir(.cwd(), io, absolute_path, options);
487}
488
111489pub fn close(dir: Dir, io: Io) void {
112 return io.vtable.dirClose(io.userdata, dir);
490 return io.vtable.dirClose(io.userdata, (&dir)[0..1]);
491}
492
493pub fn closeMany(io: Io, dirs: []const Dir) void {
494 return io.vtable.dirClose(io.userdata, dirs);
113495}
114496
115497/// Opens a file for reading or writing, without attempting to create a new file.
......@@ -125,6 +507,11 @@ pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) F
125507 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);
126508}
127509
510pub fn openFileAbsolute(io: Io, absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
511 assert(path.isAbsolute(absolute_path));
512 return openFile(.cwd(), io, absolute_path, flags);
513}
514
128515/// Creates, opens, or overwrites a file with write access.
129516///
130517/// Allocates a resource to be dellocated with `File.close`.
......@@ -136,6 +523,10 @@ pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlag
136523 return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags);
137524}
138525
526pub fn createFileAbsolute(io: Io, absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
527 return createFile(.cwd(), io, absolute_path, flags);
528}
529
139530pub const WriteFileOptions = struct {
140531 /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
141532 /// On WASI, `sub_path` should be encoded as valid UTF-8.
......@@ -145,13 +536,13 @@ pub const WriteFileOptions = struct {
145536 flags: File.CreateFlags = .{},
146537};
147538
148pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable;
539pub const WriteFileError = File.Writer.Error || File.OpenError;
149540
150541/// Writes content to the file system, using the file creation flags provided.
151542pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
152543 var file = try dir.createFile(io, options.sub_path, options.flags);
153544 defer file.close(io);
154 try file.writeAll(io, options.data);
545 try file.writeStreamingAll(io, options.data);
155546}
156547
157548pub const PrevStatus = enum {
......@@ -161,10 +552,10 @@ pub const PrevStatus = enum {
161552
162553pub const UpdateFileError = File.OpenError;
163554
164/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If
555/// Check the file size, mtime, and permissions of `source_path` and `dest_path`. If
165556/// they are equal, does nothing. Otherwise, atomically copies `source_path` to
166557/// `dest_path`, creating the parent directory hierarchy as needed. The
167/// destination file gains the mtime, atime, and mode of the source file so
558/// destination file gains the mtime, atime, and permissions of the source file so
168559/// that the next call to `updateFile` will not need a copy.
169560///
170561/// Returns the previous status of the file before updating.
......@@ -179,13 +570,13 @@ pub fn updateFile(
179570 dest_dir: Dir,
180571 /// If directories in this path do not exist, they are created.
181572 dest_path: []const u8,
182 options: std.fs.Dir.CopyFileOptions,
573 options: CopyFileOptions,
183574) !PrevStatus {
184575 var src_file = try source_dir.openFile(io, source_path, .{});
185576 defer src_file.close(io);
186577
187578 const src_stat = try src_file.stat(io);
188 const actual_mode = options.override_mode orelse src_stat.mode;
579 const actual_permissions = options.permissions orelse src_stat.permissions;
189580 check_dest_stat: {
190581 const dest_stat = blk: {
191582 var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {
......@@ -199,19 +590,19 @@ pub fn updateFile(
199590
200591 if (src_stat.size == dest_stat.size and
201592 src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and
202 actual_mode == dest_stat.mode)
593 actual_permissions == dest_stat.permissions)
203594 {
204595 return .fresh;
205596 }
206597 }
207598
208 if (std.fs.path.dirname(dest_path)) |dirname| {
209 try dest_dir.makePath(io, dirname);
599 if (path.dirname(dest_path)) |dirname| {
600 try dest_dir.createDirPath(io, dirname);
210601 }
211602
212603 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
213 var atomic_file = try std.fs.Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{
214 .mode = actual_mode,
604 var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
605 .permissions = actual_permissions,
215606 .write_buffer = &buffer,
216607 });
217608 defer atomic_file.deinit();
......@@ -224,7 +615,7 @@ pub fn updateFile(
224615 error.WriteFailed => return atomic_file.file_writer.err.?,
225616 };
226617 try atomic_file.flush();
227 try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime);
618 try atomic_file.file_writer.file.setTimestamps(io, src_stat.atime, src_stat.mtime);
228619 try atomic_file.renameIntoPlace();
229620 return .stale;
230621}
......@@ -242,7 +633,11 @@ pub const ReadFileError = File.OpenError || File.Reader.Error;
242633/// * On WASI, `file_path` should be encoded as valid UTF-8.
243634/// * On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
244635pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileError![]u8 {
245 var file = try dir.openFile(io, file_path, .{});
636 var file = try dir.openFile(io, file_path, .{
637 // We can take advantage of this on Windows since it doesn't involve any extra syscalls,
638 // so we can get error.IsDir during open rather than during the read.
639 .allow_directory = if (native_os == .windows) false else true,
640 });
246641 defer file.close(io);
247642
248643 var reader = file.reader(io, &.{});
......@@ -253,7 +648,7 @@ pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileE
253648 return buffer[0..n];
254649}
255650
256pub const MakeError = error{
651pub const CreateDirError = error{
257652 /// In WASI, this error may occur when the file descriptor does
258653 /// not hold the required rights to create a new directory relative to it.
259654 AccessDenied,
......@@ -279,21 +674,36 @@ pub const MakeError = error{
279674/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
280675///
281676/// Related:
282/// * `makePath`
283/// * `makeDirAbsolute`
284pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void {
285 return io.vtable.dirMake(io.userdata, dir, sub_path, default_mode);
677/// * `createDirPath`
678/// * `createDirAbsolute`
679pub fn createDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) CreateDirError!void {
680 return io.vtable.dirCreateDir(io.userdata, dir, sub_path, permissions);
681}
682
683/// Create a new directory, based on an absolute path.
684///
685/// Asserts that the path is absolute. See `createDir` for a function that
686/// operates on both absolute and relative paths.
687///
688/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
689/// On WASI, `absolute_path` should be encoded as valid UTF-8.
690/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
691pub fn createDirAbsolute(io: Io, absolute_path: []const u8, permissions: Permissions) CreateDirError!void {
692 assert(path.isAbsolute(absolute_path));
693 return createDir(.cwd(), io, absolute_path, permissions);
286694}
287695
288pub const MakePathError = MakeError || StatPathError;
696test createDirAbsolute {}
289697
290/// Calls makeDir iteratively to make an entire path, creating any parent
291/// directories that do not exist.
698pub const CreateDirPathError = CreateDirError || StatFileError;
699
700/// Creates parent directories with default permissions as necessary to ensure
701/// `sub_path` exists as a directory.
292702///
293703/// Returns success if the path already exists and is a directory.
294704///
295/// This function is not atomic, and if it returns an error, the file system
296/// may have been modified regardless.
705/// This function may not be atomic. If it returns an error, the file system
706/// may have been modified.
297707///
298708/// Fails on an empty path with `error.BadPathName` as that is not a path that
299709/// can be created.
......@@ -309,48 +719,29 @@ pub const MakePathError = MakeError || StatPathError;
309719/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
310720/// meaning a `sub_path` like "first/../second" will create both a `./first`
311721/// and a `./second` directory.
312pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void {
313 _ = try makePathStatus(dir, io, sub_path);
722///
723/// See also:
724/// * `createDirPathStatus`
725pub fn createDirPath(dir: Dir, io: Io, sub_path: []const u8) CreateDirPathError!void {
726 _ = try io.vtable.dirCreateDirPath(io.userdata, dir, sub_path, .default_dir);
314727}
315728
316pub const MakePathStatus = enum { existed, created };
729pub const CreatePathStatus = enum { existed, created };
317730
318/// Same as `makePath` except returns whether the path already existed or was
731/// Same as `createDirPath` except returns whether the path already existed or was
319732/// successfully created.
320pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus {
321 var it = std.fs.path.componentIterator(sub_path);
322 var status: MakePathStatus = .existed;
323 var component = it.last() orelse return error.BadPathName;
324 while (true) {
325 if (makeDir(dir, io, component.path)) {
326 status = .created;
327 } else |err| switch (err) {
328 error.PathAlreadyExists => {
329 // stat the file and return an error if it's not a directory
330 // this is important because otherwise a dangling symlink
331 // could cause an infinite loop
332 check_dir: {
333 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
334 const fstat = statPath(dir, io, component.path, .{}) catch |stat_err| switch (stat_err) {
335 error.IsDir => break :check_dir,
336 else => |e| return e,
337 };
338 if (fstat.kind != .directory) return error.NotDir;
339 }
340 },
341 error.FileNotFound => |e| {
342 component = it.previous() orelse return e;
343 continue;
344 },
345 else => |e| return e,
346 }
347 component = it.next() orelse return status;
348 }
733pub fn createDirPathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) CreateDirPathError!CreatePathStatus {
734 return io.vtable.dirCreateDirPath(io.userdata, dir, sub_path, permissions);
349735}
350736
351pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
737pub const CreateDirPathOpenError = CreateDirError || OpenError || StatFileError;
738
739pub const CreateDirPathOpenOptions = struct {
740 open_options: OpenOptions = .{},
741 permissions: Permissions = .default_dir,
742};
352743
353/// Performs the equivalent of `makePath` followed by `openDir`, atomically if possible.
744/// Performs the equivalent of `createDirPath` followed by `openDir`, atomically if possible.
354745///
355746/// When this operation is canceled, it may leave the file system in a
356747/// partially modified state.
......@@ -358,8 +749,8 @@ pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
358749/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
359750/// On WASI, `sub_path` should be encoded as valid UTF-8.
360751/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
361pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) MakeOpenPathError!Dir {
362 return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options);
752pub fn createDirPathOpen(dir: Dir, io: Io, sub_path: []const u8, options: CreateDirPathOpenOptions) CreateDirPathOpenError!Dir {
753 return io.vtable.dirCreateDirPathOpen(io.userdata, dir, sub_path, options.permissions, options.open_options);
363754}
364755
365756pub const Stat = File.Stat;
......@@ -369,9 +760,9 @@ pub fn stat(dir: Dir, io: Io) StatError!Stat {
369760 return io.vtable.dirStat(io.userdata, dir);
370761}
371762
372pub const StatPathError = File.OpenError || File.StatError;
763pub const StatFileError = File.OpenError || File.StatError;
373764
374pub const StatPathOptions = struct {
765pub const StatFileOptions = struct {
375766 follow_symlinks: bool = true,
376767};
377768
......@@ -387,6 +778,1074 @@ pub const StatPathOptions = struct {
387778/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
388779/// * On WASI, `sub_path` should be encoded as valid UTF-8.
389780/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
390pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat {
391 return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);
781pub fn statFile(dir: Dir, io: Io, sub_path: []const u8, options: StatFileOptions) StatFileError!Stat {
782 return io.vtable.dirStatFile(io.userdata, dir, sub_path, options);
783}
784
785pub const RealPathError = File.RealPathError;
786
787/// Obtains the canonicalized absolute path name of `sub_path` relative to this
788/// `Dir`. If `sub_path` is absolute, ignores this `Dir` handle and obtains the
789/// canonicalized absolute pathname of `sub_path` argument.
790///
791/// This function has limited platform support, and using it can lead to
792/// unnecessary failures and race conditions. It is generally advisable to
793/// avoid this function entirely.
794pub fn realPath(dir: Dir, io: Io, out_buffer: []u8) RealPathError!usize {
795 return io.vtable.dirRealPath(io.userdata, dir, out_buffer);
796}
797
798pub const RealPathFileError = RealPathError || PathNameError;
799
800/// Obtains the canonicalized absolute path name of `sub_path` relative to this
801/// `Dir`. If `sub_path` is absolute, ignores this `Dir` handle and obtains the
802/// canonicalized absolute pathname of `sub_path` argument.
803///
804/// This function has limited platform support, and using it can lead to
805/// unnecessary failures and race conditions. It is generally advisable to
806/// avoid this function entirely.
807///
808/// See also:
809/// * `realPathFileAlloc`.
810/// * `realPathFileAbsolute`.
811pub fn realPathFile(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathFileError!usize {
812 return io.vtable.dirRealPathFile(io.userdata, dir, sub_path, out_buffer);
813}
814
815pub const RealPathFileAllocError = RealPathFileError || Allocator.Error;
816
817/// Same as `realPathFile` except allocates result.
818///
819/// This function has limited platform support, and using it can lead to
820/// unnecessary failures and race conditions. It is generally advisable to
821/// avoid this function entirely.
822///
823/// See also:
824/// * `realPathFile`.
825/// * `realPathFileAbsolute`.
826pub fn realPathFileAlloc(dir: Dir, io: Io, sub_path: []const u8, allocator: Allocator) RealPathFileAllocError![:0]u8 {
827 var buffer: [max_path_bytes]u8 = undefined;
828 const n = try realPathFile(dir, io, sub_path, &buffer);
829 return allocator.dupeZ(u8, buffer[0..n]);
830}
831
832/// Same as `realPathFile` except `absolute_path` is asserted to be an absolute
833/// path.
834///
835/// This function has limited platform support, and using it can lead to
836/// unnecessary failures and race conditions. It is generally advisable to
837/// avoid this function entirely.
838///
839/// See also:
840/// * `realPathFile`.
841/// * `realPathFileAlloc`.
842pub fn realPathFileAbsolute(io: Io, absolute_path: []const u8, out_buffer: []u8) RealPathFileError!usize {
843 assert(path.isAbsolute(absolute_path));
844 return io.vtable.dirRealPathFile(io.userdata, .cwd(), absolute_path, out_buffer);
845}
846
847/// Same as `realPathFileAbsolute` except allocates result.
848///
849/// This function has limited platform support, and using it can lead to
850/// unnecessary failures and race conditions. It is generally advisable to
851/// avoid this function entirely.
852///
853/// See also:
854/// * `realPathFileAbsolute`.
855/// * `realPathFile`.
856pub fn realPathFileAbsoluteAlloc(io: Io, absolute_path: []const u8, allocator: Allocator) RealPathFileAllocError![:0]u8 {
857 var buffer: [max_path_bytes]u8 = undefined;
858 const n = try realPathFileAbsolute(io, absolute_path, &buffer);
859 return allocator.dupeZ(u8, buffer[0..n]);
860}
861
862pub const DeleteFileError = error{
863 FileNotFound,
864 /// In WASI, this error may occur when the file descriptor does
865 /// not hold the required rights to unlink a resource by path relative to it.
866 AccessDenied,
867 PermissionDenied,
868 FileBusy,
869 FileSystem,
870 IsDir,
871 SymLinkLoop,
872 NotDir,
873 SystemResources,
874 ReadOnlyFileSystem,
875 /// On Windows, `\\server` or `\\server\share` was not found.
876 NetworkNotFound,
877} || PathNameError || Io.Cancelable || Io.UnexpectedError;
878
879/// Delete a file name and possibly the file it refers to, based on an open directory handle.
880///
881/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
882/// On WASI, `sub_path` should be encoded as valid UTF-8.
883/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
884///
885/// Asserts that the path parameter has no null bytes.
886pub fn deleteFile(dir: Dir, io: Io, sub_path: []const u8) DeleteFileError!void {
887 return io.vtable.dirDeleteFile(io.userdata, dir, sub_path);
888}
889
890pub fn deleteFileAbsolute(io: Io, absolute_path: []const u8) DeleteFileError!void {
891 assert(path.isAbsolute(absolute_path));
892 return deleteFile(.cwd(), io, absolute_path);
893}
894
895test deleteFileAbsolute {}
896
897pub const DeleteDirError = error{
898 DirNotEmpty,
899 FileNotFound,
900 AccessDenied,
901 PermissionDenied,
902 FileBusy,
903 FileSystem,
904 SymLinkLoop,
905 NotDir,
906 SystemResources,
907 ReadOnlyFileSystem,
908 /// On Windows, `\\server` or `\\server\share` was not found.
909 NetworkNotFound,
910} || PathNameError || Io.Cancelable || Io.UnexpectedError;
911
912/// Returns `error.DirNotEmpty` if the directory is not empty.
913///
914/// To delete a directory recursively, see `deleteTree`.
915///
916/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
917/// On WASI, `sub_path` should be encoded as valid UTF-8.
918/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
919pub fn deleteDir(dir: Dir, io: Io, sub_path: []const u8) DeleteDirError!void {
920 return io.vtable.dirDeleteDir(io.userdata, dir, sub_path);
921}
922
923/// Same as `deleteDir` except the path is absolute.
924///
925/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
926/// On WASI, `dir_path` should be encoded as valid UTF-8.
927/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
928pub fn deleteDirAbsolute(io: Io, absolute_path: []const u8) DeleteDirError!void {
929 assert(path.isAbsolute(absolute_path));
930 return deleteDir(.cwd(), io, absolute_path);
931}
932
933pub const RenameError = error{
934 /// In WASI, this error may occur when the file descriptor does
935 /// not hold the required rights to rename a resource by path relative to it.
936 ///
937 /// On Windows, this error may be returned instead of PathAlreadyExists when
938 /// renaming a directory over an existing directory.
939 AccessDenied,
940 PermissionDenied,
941 FileBusy,
942 DiskQuota,
943 IsDir,
944 SymLinkLoop,
945 LinkQuotaExceeded,
946 FileNotFound,
947 NotDir,
948 SystemResources,
949 NoSpaceLeft,
950 PathAlreadyExists,
951 ReadOnlyFileSystem,
952 RenameAcrossMountPoints,
953 NoDevice,
954 SharingViolation,
955 PipeBusy,
956 /// On Windows, `\\server` or `\\server\share` was not found.
957 NetworkNotFound,
958 /// On Windows, antivirus software is enabled by default. It can be
959 /// disabled, but Windows Update sometimes ignores the user's preference
960 /// and re-enables it. When enabled, antivirus software on Windows
961 /// intercepts file system operations and makes them significantly slower
962 /// in addition to possibly failing with this error code.
963 AntivirusInterference,
964} || PathNameError || Io.Cancelable || Io.UnexpectedError;
965
966/// Change the name or location of a file or directory.
967///
968/// If `new_sub_path` already exists, it will be replaced.
969///
970/// Renaming a file over an existing directory or a directory over an existing
971/// file will fail with `error.IsDir` or `error.NotDir`
972///
973/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
974/// On WASI, both paths should be encoded as valid UTF-8.
975/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
976pub fn rename(
977 old_dir: Dir,
978 old_sub_path: []const u8,
979 new_dir: Dir,
980 new_sub_path: []const u8,
981 io: Io,
982) RenameError!void {
983 return io.vtable.dirRename(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
984}
985
986pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) RenameError!void {
987 assert(path.isAbsolute(old_path));
988 assert(path.isAbsolute(new_path));
989 const my_cwd = cwd();
990 return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);
991}
992
993pub const HardLinkOptions = struct {
994 follow_symlinks: bool = true,
995};
996
997pub const HardLinkError = error{
998 AccessDenied,
999 PermissionDenied,
1000 DiskQuota,
1001 PathAlreadyExists,
1002 HardwareFailure,
1003 /// Either the OS or the filesystem does not support hard links.
1004 OperationUnsupported,
1005 SymLinkLoop,
1006 LinkQuotaExceeded,
1007 FileNotFound,
1008 SystemResources,
1009 NoSpaceLeft,
1010 ReadOnlyFileSystem,
1011 NotSameFileSystem,
1012 NotDir,
1013} || Io.Cancelable || PathNameError || Io.UnexpectedError;
1014
1015pub fn hardLink(
1016 old_dir: Dir,
1017 old_sub_path: []const u8,
1018 new_dir: Dir,
1019 new_sub_path: []const u8,
1020 io: Io,
1021 options: HardLinkOptions,
1022) HardLinkError!void {
1023 return io.vtable.dirHardLink(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path, options);
1024}
1025
1026/// Use with `symLink`, `symLinkAtomic`, and `symLinkAbsolute` to
1027/// specify whether the symlink will point to a file or a directory. This value
1028/// is ignored on all hosts except Windows where creating symlinks to different
1029/// resource types, requires different flags. By default, `symLinkAbsolute` is
1030/// assumed to point to a file.
1031pub const SymLinkFlags = struct {
1032 is_directory: bool = false,
1033};
1034
1035pub const SymLinkError = error{
1036 /// In WASI, this error may occur when the file descriptor does
1037 /// not hold the required rights to create a new symbolic link relative to it.
1038 AccessDenied,
1039 PermissionDenied,
1040 DiskQuota,
1041 PathAlreadyExists,
1042 FileSystem,
1043 SymLinkLoop,
1044 FileNotFound,
1045 SystemResources,
1046 NoSpaceLeft,
1047 /// On Windows, `\\server` or `\\server\share` was not found.
1048 NetworkNotFound,
1049 ReadOnlyFileSystem,
1050 NotDir,
1051} || PathNameError || Io.Cancelable || Io.UnexpectedError;
1052
1053/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1054///
1055/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1056/// one; the latter case is known as a dangling link.
1057///
1058/// If `sym_link_path` exists, it will not be overwritten.
1059///
1060/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1061/// On WASI, both paths should be encoded as valid UTF-8.
1062/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1063pub fn symLink(
1064 dir: Dir,
1065 io: Io,
1066 target_path: []const u8,
1067 sym_link_path: []const u8,
1068 flags: SymLinkFlags,
1069) SymLinkError!void {
1070 return io.vtable.dirSymLink(io.userdata, dir, target_path, sym_link_path, flags);
1071}
1072
1073pub fn symLinkAbsolute(
1074 io: Io,
1075 target_path: []const u8,
1076 sym_link_path: []const u8,
1077 flags: SymLinkFlags,
1078) SymLinkError!void {
1079 assert(path.isAbsolute(target_path));
1080 assert(path.isAbsolute(sym_link_path));
1081 return symLink(.cwd(), io, target_path, sym_link_path, flags);
1082}
1083
1084/// Same as `symLink`, except tries to create the symbolic link until it
1085/// succeeds or encounters an error other than `error.PathAlreadyExists`.
1086///
1087/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1088/// * On WASI, both paths should be encoded as valid UTF-8.
1089/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1090pub fn symLinkAtomic(
1091 dir: Dir,
1092 io: Io,
1093 target_path: []const u8,
1094 sym_link_path: []const u8,
1095 flags: SymLinkFlags,
1096) !void {
1097 if (dir.symLink(io, target_path, sym_link_path, flags)) {
1098 return;
1099 } else |err| switch (err) {
1100 error.PathAlreadyExists => {},
1101 else => |e| return e,
1102 }
1103
1104 const dirname = path.dirname(sym_link_path) orelse ".";
1105
1106 const rand_len = @sizeOf(u64) * 2;
1107 const temp_path_len = dirname.len + 1 + rand_len;
1108 var temp_path_buf: [max_path_bytes]u8 = undefined;
1109
1110 if (temp_path_len > temp_path_buf.len) return error.NameTooLong;
1111 @memcpy(temp_path_buf[0..dirname.len], dirname);
1112 temp_path_buf[dirname.len] = path.sep;
1113
1114 const temp_path = temp_path_buf[0..temp_path_len];
1115
1116 while (true) {
1117 const random_integer = std.crypto.random.int(u64);
1118 temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
1119
1120 if (dir.symLink(io, target_path, temp_path, flags)) {
1121 return dir.rename(temp_path, dir, sym_link_path, io);
1122 } else |err| switch (err) {
1123 error.PathAlreadyExists => continue,
1124 else => |e| return e,
1125 }
1126 }
1127}
1128
1129pub const ReadLinkError = error{
1130 /// In WASI, this error may occur when the file descriptor does
1131 /// not hold the required rights to read value of a symbolic link relative to it.
1132 AccessDenied,
1133 PermissionDenied,
1134 FileSystem,
1135 SymLinkLoop,
1136 FileNotFound,
1137 SystemResources,
1138 NotLink,
1139 NotDir,
1140 /// Windows-only. This error may occur if the opened reparse point is
1141 /// of unsupported type.
1142 UnsupportedReparsePointType,
1143 /// On Windows, `\\server` or `\\server\share` was not found.
1144 NetworkNotFound,
1145 /// On Windows, antivirus software is enabled by default. It can be
1146 /// disabled, but Windows Update sometimes ignores the user's preference
1147 /// and re-enables it. When enabled, antivirus software on Windows
1148 /// intercepts file system operations and makes them significantly slower
1149 /// in addition to possibly failing with this error code.
1150 AntivirusInterference,
1151} || PathNameError || Io.Cancelable || Io.UnexpectedError;
1152
1153/// Obtain target of a symbolic link.
1154///
1155/// Returns how many bytes of `buffer` are populated.
1156///
1157/// Asserts that the path parameter has no null bytes.
1158///
1159/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1160/// On WASI, `sub_path` should be encoded as valid UTF-8.
1161/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1162pub fn readLink(dir: Dir, io: Io, sub_path: []const u8, buffer: []u8) ReadLinkError!usize {
1163 return io.vtable.dirReadLink(io.userdata, dir, sub_path, buffer);
1164}
1165
1166/// Same as `readLink`, except it asserts the path is absolute.
1167///
1168/// On Windows, `path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1169/// On WASI, `path` should be encoded as valid UTF-8.
1170/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
1171pub fn readLinkAbsolute(io: Io, absolute_path: []const u8, buffer: []u8) ReadLinkError!usize {
1172 assert(path.isAbsolute(absolute_path));
1173 return io.vtable.dirReadLink(io.userdata, .cwd(), absolute_path, buffer);
1174}
1175
1176pub const ReadFileAllocError = File.OpenError || File.Reader.Error || Allocator.Error || error{
1177 /// File size reached or exceeded the provided limit.
1178 StreamTooLong,
1179};
1180
1181/// Reads all the bytes from the named file. On success, caller owns returned
1182/// buffer.
1183///
1184/// If the file size is already known, a better alternative is to initialize a
1185/// `File.Reader`.
1186///
1187/// If the file size cannot be obtained, an error is returned. If
1188/// this is a realistic possibility, a better alternative is to initialize a
1189/// `File.Reader` which handles this seamlessly.
1190pub fn readFileAlloc(
1191 dir: Dir,
1192 io: Io,
1193 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1194 /// On WASI, should be encoded as valid UTF-8.
1195 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1196 sub_path: []const u8,
1197 /// Used to allocate the result.
1198 gpa: Allocator,
1199 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1200 limit: Io.Limit,
1201) ReadFileAllocError![]u8 {
1202 return readFileAllocOptions(dir, io, sub_path, gpa, limit, .of(u8), null);
1203}
1204
1205/// Reads all the bytes from the named file. On success, caller owns returned
1206/// buffer.
1207///
1208/// If the file size is already known, a better alternative is to initialize a
1209/// `File.Reader`.
1210pub fn readFileAllocOptions(
1211 dir: Dir,
1212 io: Io,
1213 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1214 /// On WASI, should be encoded as valid UTF-8.
1215 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1216 sub_path: []const u8,
1217 /// Used to allocate the result.
1218 gpa: Allocator,
1219 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1220 limit: Io.Limit,
1221 comptime alignment: std.mem.Alignment,
1222 comptime sentinel: ?u8,
1223) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
1224 var file = try dir.openFile(io, sub_path, .{
1225 // We can take advantage of this on Windows since it doesn't involve any extra syscalls,
1226 // so we can get error.IsDir during open rather than during the read.
1227 .allow_directory = if (native_os == .windows) false else true,
1228 });
1229 defer file.close(io);
1230 var file_reader = file.reader(io, &.{});
1231 return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
1232 error.ReadFailed => return file_reader.err.?,
1233 error.OutOfMemory, error.StreamTooLong => |e| return e,
1234 };
1235}
1236
1237pub const DeleteTreeError = error{
1238 AccessDenied,
1239 PermissionDenied,
1240 FileTooBig,
1241 SymLinkLoop,
1242 ProcessFdQuotaExceeded,
1243 SystemFdQuotaExceeded,
1244 NoDevice,
1245 SystemResources,
1246 ReadOnlyFileSystem,
1247 FileSystem,
1248 FileBusy,
1249 DeviceBusy,
1250 /// One of the path components was not a directory.
1251 /// This error is unreachable if `sub_path` does not contain a path separator.
1252 NotDir,
1253 /// On Windows, `\\server` or `\\server\share` was not found.
1254 NetworkNotFound,
1255} || PathNameError || Io.Cancelable || Io.UnexpectedError;
1256
1257/// Whether `sub_path` describes a symlink, file, or directory, this function
1258/// removes it. If it cannot be removed because it is a non-empty directory,
1259/// this function recursively removes its entries and then tries again.
1260///
1261/// This operation is not atomic on most file systems.
1262///
1263/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1264/// On WASI, `sub_path` should be encoded as valid UTF-8.
1265/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1266pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
1267 var initial_iterable_dir = (try dir.deleteTreeOpenInitialSubpath(io, sub_path, .file)) orelse return;
1268
1269 const StackItem = struct {
1270 name: []const u8,
1271 parent_dir: Dir,
1272 iter: Iterator,
1273
1274 fn closeAll(inner_io: Io, items: []@This()) void {
1275 for (items) |*item| item.iter.reader.dir.close(inner_io);
1276 }
1277 };
1278
1279 var stack_buffer: [16]StackItem = undefined;
1280 var stack = std.ArrayList(StackItem).initBuffer(&stack_buffer);
1281 defer StackItem.closeAll(io, stack.items);
1282
1283 stack.appendAssumeCapacity(.{
1284 .name = sub_path,
1285 .parent_dir = dir,
1286 .iter = initial_iterable_dir.iterateAssumeFirstIteration(),
1287 });
1288
1289 process_stack: while (stack.items.len != 0) {
1290 var top = &stack.items[stack.items.len - 1];
1291 while (try top.iter.next(io)) |entry| {
1292 var treat_as_dir = entry.kind == .directory;
1293 handle_entry: while (true) {
1294 if (treat_as_dir) {
1295 if (stack.unusedCapacitySlice().len >= 1) {
1296 var iterable_dir = top.iter.reader.dir.openDir(io, entry.name, .{
1297 .follow_symlinks = false,
1298 .iterate = true,
1299 }) catch |err| switch (err) {
1300 error.NotDir => {
1301 treat_as_dir = false;
1302 continue :handle_entry;
1303 },
1304 error.FileNotFound => {
1305 // That's fine, we were trying to remove this directory anyway.
1306 break :handle_entry;
1307 },
1308
1309 error.AccessDenied,
1310 error.PermissionDenied,
1311 error.SymLinkLoop,
1312 error.ProcessFdQuotaExceeded,
1313 error.NameTooLong,
1314 error.SystemFdQuotaExceeded,
1315 error.NoDevice,
1316 error.SystemResources,
1317 error.Unexpected,
1318 error.BadPathName,
1319 error.NetworkNotFound,
1320 error.DeviceBusy,
1321 error.Canceled,
1322 => |e| return e,
1323 };
1324 stack.appendAssumeCapacity(.{
1325 .name = entry.name,
1326 .parent_dir = top.iter.reader.dir,
1327 .iter = iterable_dir.iterateAssumeFirstIteration(),
1328 });
1329 continue :process_stack;
1330 } else {
1331 try top.iter.reader.dir.deleteTreeMinStackSizeWithKindHint(io, entry.name, entry.kind);
1332 break :handle_entry;
1333 }
1334 } else {
1335 if (top.iter.reader.dir.deleteFile(io, entry.name)) {
1336 break :handle_entry;
1337 } else |err| switch (err) {
1338 error.FileNotFound => break :handle_entry,
1339
1340 // Impossible because we do not pass any path separators.
1341 error.NotDir => unreachable,
1342
1343 error.IsDir => {
1344 treat_as_dir = true;
1345 continue :handle_entry;
1346 },
1347
1348 error.AccessDenied,
1349 error.PermissionDenied,
1350 error.SymLinkLoop,
1351 error.NameTooLong,
1352 error.SystemResources,
1353 error.ReadOnlyFileSystem,
1354 error.FileSystem,
1355 error.FileBusy,
1356 error.BadPathName,
1357 error.NetworkNotFound,
1358 error.Canceled,
1359 error.Unexpected,
1360 => |e| return e,
1361 }
1362 }
1363 }
1364 }
1365
1366 // On Windows, we can't delete until the dir's handle has been closed, so
1367 // close it before we try to delete.
1368 top.iter.reader.dir.close(io);
1369
1370 // In order to avoid double-closing the directory when cleaning up
1371 // the stack in the case of an error, we save the relevant portions and
1372 // pop the value from the stack.
1373 const parent_dir = top.parent_dir;
1374 const name = top.name;
1375 stack.items.len -= 1;
1376
1377 var need_to_retry: bool = false;
1378 parent_dir.deleteDir(io, name) catch |err| switch (err) {
1379 error.FileNotFound => {},
1380 error.DirNotEmpty => need_to_retry = true,
1381 else => |e| return e,
1382 };
1383
1384 if (need_to_retry) {
1385 // Since we closed the handle that the previous iterator used, we
1386 // need to re-open the dir and re-create the iterator.
1387 var iterable_dir = iterable_dir: {
1388 var treat_as_dir = true;
1389 handle_entry: while (true) {
1390 if (treat_as_dir) {
1391 break :iterable_dir parent_dir.openDir(io, name, .{
1392 .follow_symlinks = false,
1393 .iterate = true,
1394 }) catch |err| switch (err) {
1395 error.NotDir => {
1396 treat_as_dir = false;
1397 continue :handle_entry;
1398 },
1399 error.FileNotFound => {
1400 // That's fine, we were trying to remove this directory anyway.
1401 continue :process_stack;
1402 },
1403
1404 error.AccessDenied,
1405 error.PermissionDenied,
1406 error.SymLinkLoop,
1407 error.ProcessFdQuotaExceeded,
1408 error.NameTooLong,
1409 error.SystemFdQuotaExceeded,
1410 error.NoDevice,
1411 error.SystemResources,
1412 error.Unexpected,
1413 error.BadPathName,
1414 error.NetworkNotFound,
1415 error.DeviceBusy,
1416 error.Canceled,
1417 => |e| return e,
1418 };
1419 } else {
1420 if (parent_dir.deleteFile(io, name)) {
1421 continue :process_stack;
1422 } else |err| switch (err) {
1423 error.FileNotFound => continue :process_stack,
1424
1425 // Impossible because we do not pass any path separators.
1426 error.NotDir => unreachable,
1427
1428 error.IsDir => {
1429 treat_as_dir = true;
1430 continue :handle_entry;
1431 },
1432
1433 error.AccessDenied,
1434 error.PermissionDenied,
1435 error.SymLinkLoop,
1436 error.NameTooLong,
1437 error.SystemResources,
1438 error.ReadOnlyFileSystem,
1439 error.FileSystem,
1440 error.FileBusy,
1441 error.BadPathName,
1442 error.NetworkNotFound,
1443 error.Canceled,
1444 error.Unexpected,
1445 => |e| return e,
1446 }
1447 }
1448 }
1449 };
1450 // We know there is room on the stack since we are just re-adding
1451 // the StackItem that we previously popped.
1452 stack.appendAssumeCapacity(.{
1453 .name = name,
1454 .parent_dir = parent_dir,
1455 .iter = iterable_dir.iterateAssumeFirstIteration(),
1456 });
1457 continue :process_stack;
1458 }
1459 }
1460}
1461
1462/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
1463/// This is slower than `deleteTree` but uses less stack space.
1464/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1465/// On WASI, `sub_path` should be encoded as valid UTF-8.
1466/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1467pub fn deleteTreeMinStackSize(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
1468 return dir.deleteTreeMinStackSizeWithKindHint(io, sub_path, .file);
1469}
1470
1471fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
1472 start_over: while (true) {
1473 var dir = (try parent.deleteTreeOpenInitialSubpath(io, sub_path, kind_hint)) orelse return;
1474 var cleanup_dir_parent: ?Dir = null;
1475 defer if (cleanup_dir_parent) |*d| d.close(io);
1476
1477 var cleanup_dir = true;
1478 defer if (cleanup_dir) dir.close(io);
1479
1480 // Valid use of max_path_bytes because dir_name_buf will only
1481 // ever store a single path component that was returned from the
1482 // filesystem.
1483 var dir_name_buf: [max_path_bytes]u8 = undefined;
1484 var dir_name: []const u8 = sub_path;
1485
1486 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
1487 // Go through each entry and if it is not a directory, delete it. If it is a directory,
1488 // open it, and close the original directory. Repeat. Then start the entire operation over.
1489
1490 scan_dir: while (true) {
1491 var dir_it = dir.iterateAssumeFirstIteration();
1492 dir_it: while (try dir_it.next(io)) |entry| {
1493 var treat_as_dir = entry.kind == .directory;
1494 handle_entry: while (true) {
1495 if (treat_as_dir) {
1496 const new_dir = dir.openDir(io, entry.name, .{
1497 .follow_symlinks = false,
1498 .iterate = true,
1499 }) catch |err| switch (err) {
1500 error.NotDir => {
1501 treat_as_dir = false;
1502 continue :handle_entry;
1503 },
1504 error.FileNotFound => {
1505 // That's fine, we were trying to remove this directory anyway.
1506 continue :dir_it;
1507 },
1508
1509 error.AccessDenied,
1510 error.PermissionDenied,
1511 error.SymLinkLoop,
1512 error.ProcessFdQuotaExceeded,
1513 error.NameTooLong,
1514 error.SystemFdQuotaExceeded,
1515 error.NoDevice,
1516 error.SystemResources,
1517 error.Unexpected,
1518 error.BadPathName,
1519 error.NetworkNotFound,
1520 error.DeviceBusy,
1521 error.Canceled,
1522 => |e| return e,
1523 };
1524 if (cleanup_dir_parent) |*d| d.close(io);
1525 cleanup_dir_parent = dir;
1526 dir = new_dir;
1527 const result = dir_name_buf[0..entry.name.len];
1528 @memcpy(result, entry.name);
1529 dir_name = result;
1530 continue :scan_dir;
1531 } else {
1532 if (dir.deleteFile(io, entry.name)) {
1533 continue :dir_it;
1534 } else |err| switch (err) {
1535 error.FileNotFound => continue :dir_it,
1536
1537 // Impossible because we do not pass any path separators.
1538 error.NotDir => unreachable,
1539
1540 error.IsDir => {
1541 treat_as_dir = true;
1542 continue :handle_entry;
1543 },
1544
1545 error.AccessDenied,
1546 error.PermissionDenied,
1547 error.SymLinkLoop,
1548 error.NameTooLong,
1549 error.SystemResources,
1550 error.ReadOnlyFileSystem,
1551 error.FileSystem,
1552 error.FileBusy,
1553 error.BadPathName,
1554 error.NetworkNotFound,
1555 error.Canceled,
1556 error.Unexpected,
1557 => |e| return e,
1558 }
1559 }
1560 }
1561 }
1562 // Reached the end of the directory entries, which means we successfully deleted all of them.
1563 // Now to remove the directory itself.
1564 dir.close(io);
1565 cleanup_dir = false;
1566
1567 if (cleanup_dir_parent) |d| {
1568 d.deleteDir(io, dir_name) catch |err| switch (err) {
1569 // These two things can happen due to file system race conditions.
1570 error.FileNotFound, error.DirNotEmpty => continue :start_over,
1571 else => |e| return e,
1572 };
1573 continue :start_over;
1574 } else {
1575 parent.deleteDir(io, sub_path) catch |err| switch (err) {
1576 error.FileNotFound => return,
1577 error.DirNotEmpty => continue :start_over,
1578 else => |e| return e,
1579 };
1580 return;
1581 }
1582 }
1583 }
1584}
1585
1586/// On successful delete, returns null.
1587fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
1588 return iterable_dir: {
1589 // Treat as a file by default
1590 var treat_as_dir = kind_hint == .directory;
1591
1592 handle_entry: while (true) {
1593 if (treat_as_dir) {
1594 break :iterable_dir dir.openDir(io, sub_path, .{
1595 .follow_symlinks = false,
1596 .iterate = true,
1597 }) catch |err| switch (err) {
1598 error.NotDir => {
1599 treat_as_dir = false;
1600 continue :handle_entry;
1601 },
1602 error.FileNotFound => {
1603 // That's fine, we were trying to remove this directory anyway.
1604 return null;
1605 },
1606
1607 error.AccessDenied,
1608 error.PermissionDenied,
1609 error.SymLinkLoop,
1610 error.ProcessFdQuotaExceeded,
1611 error.NameTooLong,
1612 error.SystemFdQuotaExceeded,
1613 error.NoDevice,
1614 error.SystemResources,
1615 error.Unexpected,
1616 error.BadPathName,
1617 error.DeviceBusy,
1618 error.NetworkNotFound,
1619 error.Canceled,
1620 => |e| return e,
1621 };
1622 } else {
1623 if (dir.deleteFile(io, sub_path)) {
1624 return null;
1625 } else |err| switch (err) {
1626 error.FileNotFound => return null,
1627
1628 error.IsDir => {
1629 treat_as_dir = true;
1630 continue :handle_entry;
1631 },
1632
1633 error.AccessDenied,
1634 error.PermissionDenied,
1635 error.SymLinkLoop,
1636 error.NameTooLong,
1637 error.SystemResources,
1638 error.ReadOnlyFileSystem,
1639 error.NotDir,
1640 error.FileSystem,
1641 error.FileBusy,
1642 error.BadPathName,
1643 error.NetworkNotFound,
1644 error.Canceled,
1645 error.Unexpected,
1646 => |e| return e,
1647 }
1648 }
1649 }
1650 };
1651}
1652
1653pub const CopyFileOptions = struct {
1654 /// When this is `null` the permissions are copied from the source file.
1655 permissions: ?File.Permissions = null,
1656};
1657
1658pub const CopyFileError = File.OpenError || File.StatError ||
1659 File.Atomic.InitError || File.Atomic.FinishError ||
1660 File.Reader.Error || File.Writer.Error || error{InvalidFileName};
1661
1662/// Atomically creates a new file at `dest_path` within `dest_dir` with the
1663/// same contents as `source_path` within `source_dir`, overwriting any already
1664/// existing file.
1665///
1666/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
1667/// readily available, there is a possibility of power loss or application
1668/// termination leaving temporary files present in the same directory as
1669/// dest_path.
1670///
1671/// On Windows, both paths should be encoded as
1672/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be
1673/// encoded as valid UTF-8. On other platforms, both paths are an opaque
1674/// sequence of bytes with no particular encoding.
1675pub fn copyFile(
1676 source_dir: Dir,
1677 source_path: []const u8,
1678 dest_dir: Dir,
1679 dest_path: []const u8,
1680 io: Io,
1681 options: CopyFileOptions,
1682) CopyFileError!void {
1683 const file = try source_dir.openFile(io, source_path, .{});
1684 var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
1685 defer file_reader.file.close(io);
1686
1687 const permissions = options.permissions orelse blk: {
1688 const st = try file_reader.file.stat(io);
1689 file_reader.size = st.size;
1690 break :blk st.permissions;
1691 };
1692
1693 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
1694 var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
1695 .permissions = permissions,
1696 .write_buffer = &buffer,
1697 });
1698 defer atomic_file.deinit();
1699
1700 _ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
1701 error.ReadFailed => return file_reader.err.?,
1702 error.WriteFailed => return atomic_file.file_writer.err.?,
1703 };
1704
1705 try atomic_file.finish();
1706}
1707
1708/// Same as `copyFile`, except asserts that both `source_path` and `dest_path`
1709/// are absolute.
1710///
1711/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1712/// On WASI, both paths should be encoded as valid UTF-8.
1713/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1714pub fn copyFileAbsolute(
1715 source_path: []const u8,
1716 dest_path: []const u8,
1717 io: Io,
1718 options: CopyFileOptions,
1719) !void {
1720 assert(path.isAbsolute(source_path));
1721 assert(path.isAbsolute(dest_path));
1722 const my_cwd = cwd();
1723 return copyFile(my_cwd, source_path, my_cwd, dest_path, io, options);
1724}
1725
1726test copyFileAbsolute {}
1727
1728pub const AtomicFileOptions = struct {
1729 permissions: File.Permissions = .default_file,
1730 make_path: bool = false,
1731 write_buffer: []u8,
1732};
1733
1734/// Directly access the `.file` field, and then call `File.Atomic.finish` to
1735/// atomically replace `dest_path` with contents.
1736///
1737/// Always call `File.Atomic.deinit` to clean up, regardless of whether
1738/// `File.Atomic.finish` succeeded. `dest_path` must remain valid until
1739/// `File.Atomic.deinit` is called.
1740///
1741/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1742/// On WASI, `dest_path` should be encoded as valid UTF-8.
1743/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
1744pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic {
1745 if (path.dirname(dest_path)) |dirname| {
1746 const dir = if (options.make_path)
1747 try parent.createDirPathOpen(io, dirname, .{})
1748 else
1749 try parent.openDir(io, dirname, .{});
1750
1751 return .init(io, path.basename(dest_path), options.permissions, dir, true, options.write_buffer);
1752 } else {
1753 return .init(io, dest_path, options.permissions, parent, false, options.write_buffer);
1754 }
1755}
1756
1757pub const SetPermissionsError = File.SetPermissionsError;
1758pub const Permissions = File.Permissions;
1759
1760/// Also known as "chmod".
1761///
1762/// The process must have the correct privileges in order to do this
1763/// successfully, or must have the effective user ID matching the owner
1764/// of the directory. Additionally, the directory must have been opened
1765/// with `OpenOptions.iterate` set to `true`.
1766pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPermissionsError!void {
1767 return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions);
1768}
1769
1770pub const SetFilePermissionsError = PathNameError || SetPermissionsError || error{
1771 ProcessFdQuotaExceeded,
1772 SystemFdQuotaExceeded,
1773 /// `SetFilePermissionsOptions.follow_symlinks` was set to false, which is
1774 /// not allowed by the file system or operating system.
1775 OperationUnsupported,
1776};
1777
1778pub const SetFilePermissionsOptions = struct {
1779 follow_symlinks: bool = true,
1780};
1781
1782/// Also known as "fchmodat".
1783pub fn setFilePermissions(
1784 dir: Dir,
1785 io: Io,
1786 sub_path: []const u8,
1787 new_permissions: File.Permissions,
1788 options: SetFilePermissionsOptions,
1789) SetFilePermissionsError!void {
1790 return io.vtable.dirSetFilePermissions(io.userdata, dir, sub_path, new_permissions, options);
1791}
1792
1793pub const SetOwnerError = File.SetOwnerError;
1794
1795/// Also known as "chown".
1796///
1797/// The process must have the correct privileges in order to do this
1798/// successfully. The group may be changed by the owner of the directory to
1799/// any group of which the owner is a member. Additionally, the directory
1800/// must have been opened with `OpenOptions.iterate` set to `true`. If the
1801/// owner or group is specified as `null`, the ID is not changed.
1802pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerError!void {
1803 return io.vtable.dirSetOwner(io.userdata, dir, owner, group);
1804}
1805
1806pub const SetFileOwnerError = PathNameError || SetOwnerError;
1807
1808pub const SetFileOwnerOptions = struct {
1809 follow_symlinks: bool = true,
1810};
1811
1812/// Also known as "fchownat".
1813pub fn setFileOwner(
1814 dir: Dir,
1815 io: Io,
1816 sub_path: []const u8,
1817 owner: ?File.Uid,
1818 group: ?File.Gid,
1819 options: SetFileOwnerOptions,
1820) SetOwnerError!void {
1821 return io.vtable.dirSetFileOwner(io.userdata, dir, sub_path, owner, group, options);
1822}
1823
1824pub const SetTimestampsError = File.SetTimestampsError || PathNameError;
1825
1826pub const SetTimestampsOptions = struct {
1827 follow_symlinks: bool = true,
1828};
1829
1830/// The granularity that ultimately is stored depends on the combination of
1831/// operating system and file system. When a value as provided that exceeds
1832/// this range, the value is clamped to the maximum.
1833pub fn setTimestamps(
1834 dir: Dir,
1835 io: Io,
1836 sub_path: []const u8,
1837 last_accessed: Io.Timestamp,
1838 last_modified: Io.Timestamp,
1839 options: SetTimestampsOptions,
1840) SetTimestampsError!void {
1841 return io.vtable.dirSetTimestamps(io.userdata, dir, sub_path, last_accessed, last_modified, options);
1842}
1843
1844/// Sets the accessed and modification timestamps of the provided path to the
1845/// current wall clock time.
1846///
1847/// The granularity that ultimately is stored depends on the combination of
1848/// operating system and file system.
1849pub fn setTimestampsNow(dir: Dir, io: Io, sub_path: []const u8, options: SetTimestampsOptions) SetTimestampsError!void {
1850 return io.vtable.fileSetTimestampsNow(io.userdata, dir, sub_path, options);
3921851}
lib/std/Io/File.zig+446-407
......@@ -7,12 +7,19 @@ const is_windows = native_os == .windows;
77const std = @import("../std.zig");
88const Io = std.Io;
99const assert = std.debug.assert;
10const Dir = std.Io.Dir;
1011
1112handle: Handle,
1213
14pub const Reader = @import("File/Reader.zig");
15pub const Writer = @import("File/Writer.zig");
16pub const Atomic = @import("File/Atomic.zig");
17
1318pub const Handle = std.posix.fd_t;
14pub const Mode = std.posix.mode_t;
1519pub const INode = std.posix.ino_t;
20pub const NLink = std.posix.nlink_t;
21pub const Uid = std.posix.uid_t;
22pub const Gid = std.posix.gid_t;
1623
1724pub const Kind = enum {
1825 block_device,
......@@ -41,9 +48,9 @@ pub const Stat = struct {
4148 /// The FileIndex on Windows is similar. It is a number for a file that
4249 /// is unique to each filesystem.
4350 inode: INode,
51 nlink: NLink,
4452 size: u64,
45 /// This is available on POSIX systems and is always 0 otherwise.
46 mode: Mode,
53 permissions: Permissions,
4754 kind: Kind,
4855 /// Last access time in nanoseconds, relative to UTC 1970-01-01.
4956 atime: Io.Timestamp,
......@@ -95,6 +102,26 @@ pub const Lock = enum {
95102pub const OpenFlags = struct {
96103 mode: OpenMode = .read_only,
97104
105 /// Determines the behavior when opening a path that refers to a directory.
106 ///
107 /// If set to true, directories may be opened, but `error.IsDir` is still
108 /// possible in certain scenarios, e.g. attempting to open a directory with
109 /// write permissions.
110 ///
111 /// If set to false, `error.IsDir` will always be returned when opening a directory.
112 ///
113 /// When set to false:
114 /// * On Windows, the behavior is implemented without any extra syscalls.
115 /// * On other operating systems, the behavior is implemented with an additional
116 /// `fstat` syscall.
117 allow_directory: bool = true,
118 /// Indicates intent for only some operations to be performed on this
119 /// opened file:
120 /// * `close`
121 /// * `stat`
122 /// On Linux and FreeBSD, this corresponds to `std.posix.O.PATH`.
123 path_only: bool = false,
124
98125 /// Open the file with an advisory lock to coordinate with other processes
99126 /// accessing it at the same time. An exclusive lock will prevent other
100127 /// processes from acquiring a lock. A shared lock will prevent other
......@@ -141,7 +168,51 @@ pub const OpenFlags = struct {
141168 }
142169};
143170
144pub const CreateFlags = std.fs.File.CreateFlags;
171pub const CreateFlags = struct {
172 /// Whether the file will be created with read access.
173 read: bool = false,
174
175 /// If the file already exists, and is a regular file, and the access
176 /// mode allows writing, it will be truncated to length 0.
177 truncate: bool = true,
178
179 /// Ensures that this open call creates the file, otherwise causes
180 /// `error.PathAlreadyExists` to be returned.
181 exclusive: bool = false,
182
183 /// Open the file with an advisory lock to coordinate with other processes
184 /// accessing it at the same time. An exclusive lock will prevent other
185 /// processes from acquiring a lock. A shared lock will prevent other
186 /// processes from acquiring a exclusive lock, but does not prevent
187 /// other process from getting their own shared locks.
188 ///
189 /// The lock is advisory, except on Linux in very specific circumstances[1].
190 /// This means that a process that does not respect the locking API can still get access
191 /// to the file, despite the lock.
192 ///
193 /// On these operating systems, the lock is acquired atomically with
194 /// opening the file:
195 /// * Darwin
196 /// * DragonFlyBSD
197 /// * FreeBSD
198 /// * Haiku
199 /// * NetBSD
200 /// * OpenBSD
201 /// On these operating systems, the lock is acquired via a separate syscall
202 /// after opening the file:
203 /// * Linux
204 /// * Windows
205 ///
206 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
207 lock: Lock = .none,
208
209 /// Sets whether or not to wait until the file is locked to return. If set to true,
210 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
211 /// is available to proceed.
212 lock_nonblocking: bool = false,
213
214 permissions: Permissions = .default_file,
215};
145216
146217pub const OpenError = error{
147218 SharingViolation,
......@@ -149,7 +220,6 @@ pub const OpenError = error{
149220 NoDevice,
150221 /// On Windows, `\\server` or `\\server\share` was not found.
151222 NetworkNotFound,
152 ProcessNotFound,
153223 /// On Windows, antivirus software is enabled by default. It can be
154224 /// disabled, but Windows Update sometimes ignores the user's preference
155225 /// and re-enables it. When enabled, antivirus software on Windows
......@@ -178,7 +248,9 @@ pub const OpenError = error{
178248 /// The file is too large to be opened. This error is unreachable
179249 /// for 64-bit targets, as well as when opening directories.
180250 FileTooBig,
181 /// The path refers to directory but the `DIRECTORY` flag was not provided.
251 /// Either:
252 /// * The path refers to a directory and write permissions were requested.
253 /// * The path refers to a directory and `allow_directory` was set to false.
182254 IsDir,
183255 /// A new path cannot be created because the device has no room for the new file.
184256 /// This error is only reachable when the `CREAT` flag is provided.
......@@ -189,7 +261,7 @@ pub const OpenError = error{
189261 /// The path already exists and the `CREAT` and `EXCL` flags were provided.
190262 PathAlreadyExists,
191263 DeviceBusy,
192 FileLocksNotSupported,
264 FileLocksUnsupported,
193265 /// One of these three things:
194266 /// * pathname refers to an executable image which is currently being
195267 /// executed and write access was requested.
......@@ -204,451 +276,418 @@ pub const OpenError = error{
204276} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
205277
206278pub fn close(file: File, io: Io) void {
207 return io.vtable.fileClose(io.userdata, file);
279 return io.vtable.fileClose(io.userdata, (&file)[0..1]);
208280}
209281
210pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || std.posix.FlockError;
211
212pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File {
213 return io.vtable.openSelfExe(io.userdata, flags);
282pub fn closeMany(io: Io, files: []const File) void {
283 return io.vtable.fileClose(io.userdata, files);
214284}
215285
216pub const ReadPositionalError = Reader.Error || error{Unseekable};
286pub const SyncError = error{
287 InputOutput,
288 NoSpaceLeft,
289 DiskQuota,
290 AccessDenied,
291} || Io.Cancelable || Io.UnexpectedError;
217292
218pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPositionalError!usize {
219 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
293/// Blocks until all pending file contents and metadata modifications for the
294/// file have been synchronized with the underlying filesystem.
295///
296/// This does not ensure that metadata for the directory containing the file
297/// has also reached disk.
298pub fn sync(file: File, io: Io) SyncError!void {
299 return io.vtable.fileSync(io.userdata, file);
220300}
221301
222pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable;
223
224pub fn writeStreaming(file: File, io: Io, buffer: [][]const u8) WriteStreamingError!usize {
225 return file.fileWriteStreaming(io, buffer);
302/// Test whether the file refers to a terminal (similar to libc "isatty").
303///
304/// See also:
305/// * `enableAnsiEscapeCodes`
306/// * `supportsAnsiEscapeCodes`.
307pub fn isTty(file: File, io: Io) Io.Cancelable!bool {
308 return io.vtable.fileIsTty(io.userdata, file);
226309}
227310
228pub const WritePositionalError = WriteStreamingError || error{Unseekable};
311pub const EnableAnsiEscapeCodesError = error{
312 NotTerminalDevice,
313} || Io.Cancelable || Io.UnexpectedError;
229314
230pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize {
231 return io.vtable.fileWritePositional(io.userdata, file, buffer, offset);
315pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError!void {
316 return io.vtable.fileEnableAnsiEscapeCodes(io.userdata, file);
232317}
233318
234pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File {
235 assert(std.fs.path.isAbsolute(absolute_path));
236 return Io.Dir.cwd().openFile(io, absolute_path, flags);
319/// Test whether ANSI escape codes will be treated as such without
320/// attempting to enable support for ANSI escape codes.
321pub fn supportsAnsiEscapeCodes(file: File, io: Io) Io.Cancelable!bool {
322 return io.vtable.fileSupportsAnsiEscapeCodes(io.userdata, file);
237323}
238324
239/// Defaults to positional reading; falls back to streaming.
325pub const SetLengthError = error{
326 FileTooBig,
327 InputOutput,
328 FileBusy,
329 AccessDenied,
330 PermissionDenied,
331 NonResizable,
332} || Io.Cancelable || Io.UnexpectedError;
333
334/// Truncates or expands the file, populating any new data with zeroes.
240335///
241/// Positional is more threadsafe, since the global seek position is not
242/// affected.
243pub fn reader(file: File, io: Io, buffer: []u8) Reader {
244 return .init(file, io, buffer);
336/// The file offset after this call is left unchanged.
337pub fn setLength(file: File, io: Io, new_length: u64) SetLengthError!void {
338 return io.vtable.fileSetLength(io.userdata, file, new_length);
245339}
246340
247/// Positional is more threadsafe, since the global seek position is not
248/// affected, but when such syscalls are not available, preemptively
249/// initializing in streaming mode skips a failed syscall.
250pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
251 return .initStreaming(file, io, buffer);
341pub const LengthError = StatError;
342
343/// Retrieve the ending byte index of the file.
344///
345/// Sometimes cheaper than `stat` if only the length is needed.
346pub fn length(file: File, io: Io) LengthError!u64 {
347 return io.vtable.fileLength(io.userdata, file);
252348}
253349
254pub const SeekError = error{
255 Unseekable,
256 /// The file descriptor does not hold the required rights to seek on it.
350pub const SetPermissionsError = error{
257351 AccessDenied,
352 PermissionDenied,
353 InputOutput,
354 SymLinkLoop,
355 FileNotFound,
356 SystemResources,
357 ReadOnlyFileSystem,
258358} || Io.Cancelable || Io.UnexpectedError;
259359
260/// Memoizes key information about a file handle such as:
261/// * The size from calling stat, or the error that occurred therein.
262/// * The current seek position.
263/// * The error that occurred when trying to seek.
264/// * Whether reading should be done positionally or streaming.
265/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`)
266/// versus plain variants (e.g. `read`).
360/// Also known as "chmod".
267361///
268/// Fulfills the `Io.Reader` interface.
269pub const Reader = struct {
270 io: Io,
271 file: File,
272 err: ?Error = null,
273 mode: Reader.Mode = .positional,
274 /// Tracks the true seek position in the file. To obtain the logical
275 /// position, use `logicalPos`.
276 pos: u64 = 0,
277 size: ?u64 = null,
278 size_err: ?SizeError = null,
279 seek_err: ?Reader.SeekError = null,
280 interface: Io.Reader,
281
282 pub const Error = error{
283 InputOutput,
284 SystemResources,
285 IsDir,
286 BrokenPipe,
287 ConnectionResetByPeer,
288 Timeout,
289 /// In WASI, EBADF is mapped to this error because it is returned when
290 /// trying to read a directory file descriptor as if it were a file.
291 NotOpenForReading,
292 SocketUnconnected,
293 /// This error occurs when no global event loop is configured,
294 /// and reading from the file descriptor would block.
295 WouldBlock,
296 /// In WASI, this error occurs when the file descriptor does
297 /// not hold the required rights to read from it.
298 AccessDenied,
299 /// This error occurs in Linux if the process to be read from
300 /// no longer exists.
301 ProcessNotFound,
302 /// Unable to read file due to lock.
303 LockViolation,
304 } || Io.Cancelable || Io.UnexpectedError;
305
306 pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{
307 /// Occurs if, for example, the file handle is a network socket and therefore does not have a size.
308 Streaming,
309 };
310
311 pub const SeekError = File.SeekError || error{
312 /// Seeking fell back to reading, and reached the end before the requested seek position.
313 /// `pos` remains at the end of the file.
314 EndOfStream,
315 /// Seeking fell back to reading, which failed.
316 ReadFailed,
317 };
318
319 pub const Mode = enum {
320 streaming,
321 positional,
322 /// Avoid syscalls other than `read` and `readv`.
323 streaming_reading,
324 /// Avoid syscalls other than `pread` and `preadv`.
325 positional_reading,
326 /// Indicates reading cannot continue because of a seek failure.
327 failure,
328
329 pub fn toStreaming(m: @This()) @This() {
330 return switch (m) {
331 .positional, .streaming => .streaming,
332 .positional_reading, .streaming_reading => .streaming_reading,
333 .failure => .failure,
334 };
335 }
336
337 pub fn toReading(m: @This()) @This() {
338 return switch (m) {
339 .positional, .positional_reading => .positional_reading,
340 .streaming, .streaming_reading => .streaming_reading,
341 .failure => .failure,
342 };
343 }
344 };
345
346 pub fn initInterface(buffer: []u8) Io.Reader {
347 return .{
348 .vtable = &.{
349 .stream = Reader.stream,
350 .discard = Reader.discard,
351 .readVec = Reader.readVec,
352 },
353 .buffer = buffer,
354 .seek = 0,
355 .end = 0,
356 };
357 }
362/// The process must have the correct privileges in order to do this
363/// successfully, or must have the effective user ID matching the owner of the
364/// file.
365pub fn setPermissions(file: File, io: Io, new_permissions: Permissions) SetPermissionsError!void {
366 return io.vtable.fileSetPermissions(io.userdata, file, new_permissions);
367}
358368
359 pub fn init(file: File, io: Io, buffer: []u8) Reader {
360 return .{
361 .io = io,
362 .file = file,
363 .interface = initInterface(buffer),
364 };
365 }
369pub const SetOwnerError = error{
370 AccessDenied,
371 PermissionDenied,
372 InputOutput,
373 SymLinkLoop,
374 FileNotFound,
375 SystemResources,
376 ReadOnlyFileSystem,
377} || Io.Cancelable || Io.UnexpectedError;
366378
367 /// Takes a legacy `std.fs.File` to help with upgrading.
368 pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader {
369 return .init(.{ .handle = file.handle }, io, buffer);
370 }
379/// Also known as "chown".
380///
381/// The process must have the correct privileges in order to do this
382/// successfully. The group may be changed by the owner of the file to any
383/// group of which the owner is a member. If the owner or group is specified as
384/// `null`, the ID is not changed.
385pub fn setOwner(file: File, io: Io, owner: ?Uid, group: ?Gid) SetOwnerError!void {
386 return io.vtable.fileSetOwner(io.userdata, file, owner, group);
387}
371388
372 pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
373 return .{
374 .io = io,
375 .file = file,
376 .interface = initInterface(buffer),
377 .size = size,
378 };
379 }
389/// Cross-platform representation of permissions on a file.
390///
391/// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes".
392pub const Permissions = std.Options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) {
393 default_file = 0,
394 _,
395
396 pub const default_dir: @This() = .default_file;
397 pub const executable_file: @This() = .default_file;
398 pub const has_executable_bit = false;
399
400 const windows = std.os.windows;
380401
381 /// Positional is more threadsafe, since the global seek position is not
382 /// affected, but when such syscalls are not available, preemptively
383 /// initializing in streaming mode skips a failed syscall.
384 pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader {
385 return .{
386 .io = io,
387 .file = file,
388 .interface = Reader.initInterface(buffer),
389 .mode = .streaming,
390 .seek_err = error.Unseekable,
391 .size_err = error.Streaming,
392 };
402 pub fn toAttributes(self: @This()) windows.FILE.ATTRIBUTE {
403 return @bitCast(@intFromEnum(self));
393404 }
394405
395 pub fn getSize(r: *Reader) SizeError!u64 {
396 return r.size orelse {
397 if (r.size_err) |err| return err;
398 if (stat(r.file, r.io)) |st| {
399 if (st.kind == .file) {
400 r.size = st.size;
401 return st.size;
402 } else {
403 r.mode = r.mode.toStreaming();
404 r.size_err = error.Streaming;
405 return error.Streaming;
406 }
407 } else |err| {
408 r.size_err = err;
409 return err;
410 }
411 };
406 pub fn readOnly(self: @This()) bool {
407 const attributes = toAttributes(self);
408 return attributes & windows.FILE_ATTRIBUTE_READONLY != 0;
412409 }
413410
414 pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
415 const io = r.io;
416 switch (r.mode) {
417 .positional, .positional_reading => {
418 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
419 },
420 .streaming, .streaming_reading => {
421 const seek_err = r.seek_err orelse e: {
422 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) {
423 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
424 return;
425 } else |err| {
426 r.seek_err = err;
427 break :e err;
428 }
429 };
430 var remaining = std.math.cast(u64, offset) orelse return seek_err;
431 while (remaining > 0) {
432 remaining -= discard(&r.interface, .limited64(remaining)) catch |err| {
433 r.seek_err = err;
434 return err;
435 };
436 }
437 r.interface.tossBuffered();
438 },
439 .failure => return r.seek_err.?,
440 }
411 pub fn setReadOnly(self: @This(), read_only: bool) @This() {
412 const attributes = toAttributes(self);
413 return @enumFromInt(if (read_only)
414 attributes | windows.FILE_ATTRIBUTE_READONLY
415 else
416 attributes & ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY));
417 }
418} else if (std.posix.mode_t != u0) enum(std.posix.mode_t) {
419 /// This is the default mode given to POSIX operating systems for creating
420 /// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
421 /// since most people would expect "-rw-r--r--", for example, when using
422 /// the `touch` command, which would correspond to `0o644`. However, POSIX
423 /// libc implementations use `0o666` inside `fopen` and then rely on the
424 /// process-scoped "umask" setting to adjust this number for file creation.
425 default_file = 0o666,
426 default_dir = 0o755,
427 executable_file = 0o777,
428 _,
429
430 pub const has_executable_bit = native_os != .wasi;
431
432 pub fn toMode(self: @This()) std.posix.mode_t {
433 return @intFromEnum(self);
441434 }
442435
443 /// Repositions logical read offset relative to the beginning of the file.
444 pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
445 const io = r.io;
446 switch (r.mode) {
447 .positional, .positional_reading => {
448 setLogicalPos(r, offset);
449 },
450 .streaming, .streaming_reading => {
451 const logical_pos = logicalPos(r);
452 if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos));
453 if (r.seek_err) |err| return err;
454 io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
455 r.seek_err = err;
456 return err;
457 };
458 setLogicalPos(r, offset);
459 },
460 .failure => return r.seek_err.?,
461 }
436 pub fn fromMode(mode: std.posix.mode_t) @This() {
437 return @enumFromInt(mode);
462438 }
463439
464 pub fn logicalPos(r: *const Reader) u64 {
465 return r.pos - r.interface.bufferedLen();
440 /// Returns `true` if and only if no class has write permissions.
441 pub fn readOnly(self: @This()) bool {
442 const mode = toMode(self);
443 return mode & 0o222 == 0;
466444 }
467445
468 fn setLogicalPos(r: *Reader, offset: u64) void {
469 const logical_pos = r.logicalPos();
470 if (offset < logical_pos or offset >= r.pos) {
471 r.interface.tossBuffered();
472 r.pos = offset;
473 } else r.interface.toss(@intCast(offset - logical_pos));
446 /// Enables write permission for all classes.
447 pub fn setReadOnly(self: @This(), read_only: bool) @This() {
448 const mode = toMode(self);
449 const o222 = @as(std.posix.mode_t, 0o222);
450 return @enumFromInt(if (read_only) mode & ~o222 else mode | o222);
474451 }
452} else enum(u0) {
453 default_file = 0,
454 pub const default_dir: @This() = .default_file;
455 pub const executable_file: @This() = .default_file;
456 pub const has_executable_bit = false;
457};
475458
476 /// Number of slices to store on the stack, when trying to send as many byte
477 /// vectors through the underlying read calls as possible.
478 const max_buffers_len = 16;
459pub const SetTimestampsError = error{
460 /// times is NULL, or both nsec values are UTIME_NOW, and either:
461 /// * the effective user ID of the caller does not match the owner
462 /// of the file, the caller does not have write access to the
463 /// file, and the caller is not privileged (Linux: does not have
464 /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability);
465 /// or,
466 /// * the file is marked immutable (see chattr(1)).
467 AccessDenied,
468 /// The caller attempted to change one or both timestamps to a value
469 /// other than the current time, or to change one of the timestamps
470 /// to the current time while leaving the other timestamp unchanged,
471 /// (i.e., times is not NULL, neither nsec field is UTIME_NOW,
472 /// and neither nsec field is UTIME_OMIT) and either:
473 /// * the caller's effective user ID does not match the owner of
474 /// file, and the caller is not privileged (Linux: does not have
475 /// the CAP_FOWNER capability); or,
476 /// * the file is marked append-only or immutable (see chattr(1)).
477 PermissionDenied,
478 ReadOnlyFileSystem,
479} || Io.Cancelable || Io.UnexpectedError;
479480
480 fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
481 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
482 return streamMode(r, w, limit, r.mode);
483 }
481/// The granularity that ultimately is stored depends on the combination of
482/// operating system and file system. When a value as provided that exceeds
483/// this range, the value is clamped to the maximum.
484pub fn setTimestamps(
485 file: File,
486 io: Io,
487 last_accessed: Io.Timestamp,
488 last_modified: Io.Timestamp,
489) SetTimestampsError!void {
490 return io.vtable.fileSetTimestamps(io.userdata, file, last_accessed, last_modified);
491}
484492
485 pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize {
486 switch (mode) {
487 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
488 error.Unimplemented => {
489 r.mode = r.mode.toReading();
490 return 0;
491 },
492 else => |e| return e,
493 },
494 .positional_reading => {
495 const dest = limit.slice(try w.writableSliceGreedy(1));
496 var data: [1][]u8 = .{dest};
497 const n = try readVecPositional(r, &data);
498 w.advance(n);
499 return n;
500 },
501 .streaming_reading => {
502 const dest = limit.slice(try w.writableSliceGreedy(1));
503 var data: [1][]u8 = .{dest};
504 const n = try readVecStreaming(r, &data);
505 w.advance(n);
506 return n;
507 },
508 .failure => return error.ReadFailed,
509 }
510 }
493/// Sets the accessed and modification timestamps of `file` to the current wall
494/// clock time.
495///
496/// The granularity that ultimately is stored depends on the combination of
497/// operating system and file system.
498pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {
499 return io.vtable.fileSetTimestampsNow(io.userdata, file);
500}
511501
512 fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
513 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
514 switch (r.mode) {
515 .positional, .positional_reading => return readVecPositional(r, data),
516 .streaming, .streaming_reading => return readVecStreaming(r, data),
517 .failure => return error.ReadFailed,
518 }
519 }
502/// Returns 0 on stream end or if `buffer` has no space available for data.
503///
504/// See also:
505/// * `reader`
506pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize {
507 return io.vtable.fileReadStreaming(io.userdata, file, buffer);
508}
520509
521 fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
522 const io = r.io;
523 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
524 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
525 const dest = iovecs_buffer[0..dest_n];
526 assert(dest[0].len > 0);
527 const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) {
528 error.Unseekable => {
529 r.mode = r.mode.toStreaming();
530 const pos = r.pos;
531 if (pos != 0) {
532 r.pos = 0;
533 r.seekBy(@intCast(pos)) catch {
534 r.mode = .failure;
535 return error.ReadFailed;
536 };
537 }
538 return 0;
539 },
540 else => |e| {
541 r.err = e;
542 return error.ReadFailed;
543 },
544 };
545 if (n == 0) {
546 r.size = r.pos;
547 return error.EndOfStream;
548 }
549 r.pos += n;
550 if (n > data_size) {
551 r.interface.end += n - data_size;
552 return data_size;
553 }
554 return n;
555 }
510pub const ReadPositionalError = Reader.Error || error{Unseekable};
556511
557 fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
558 const io = r.io;
559 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
560 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
561 const dest = iovecs_buffer[0..dest_n];
562 assert(dest[0].len > 0);
563 const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| {
564 r.err = err;
565 return error.ReadFailed;
566 };
567 if (n == 0) {
568 r.size = r.pos;
569 return error.EndOfStream;
570 }
571 r.pos += n;
572 if (n > data_size) {
573 r.interface.end += n - data_size;
574 return data_size;
575 }
576 return n;
577 }
512/// Returns 0 on stream end or if `buffer` has no space available for data.
513///
514/// See also:
515/// * `reader`
516pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) ReadPositionalError!usize {
517 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
518}
519
520pub const WritePositionalError = Writer.Error || error{Unseekable};
521
522/// See also:
523/// * `writer`
524pub fn writePositional(file: File, io: Io, buffer: []const []const u8, offset: u64) WritePositionalError!usize {
525 return io.vtable.fileWritePositional(io.userdata, file, &.{}, buffer, 1, offset);
526}
527
528/// Equivalent to creating a positional writer, writing `bytes`, and then flushing.
529pub fn writePositionalAll(file: File, io: Io, bytes: []const u8, offset: u64) WritePositionalError!void {
530 var index: usize = 0;
531 while (index < bytes.len)
532 index += try io.vtable.fileWritePositional(io.userdata, file, &.{}, &.{bytes[index..]}, 1, offset + index);
533}
534
535pub const SeekError = error{
536 Unseekable,
537 /// The file descriptor does not hold the required rights to seek on it.
538 AccessDenied,
539} || Io.Cancelable || Io.UnexpectedError;
540
541pub const WriteFilePositionalError = Writer.WriteFileError || error{Unseekable};
542
543/// Defaults to positional reading; falls back to streaming.
544///
545/// Positional is more threadsafe, since the global seek position is not
546/// affected.
547///
548/// See also:
549/// * `readerStreaming`
550pub fn reader(file: File, io: Io, buffer: []u8) Reader {
551 return .init(file, io, buffer);
552}
578553
579 fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
580 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
581 const io = r.io;
582 const file = r.file;
583 switch (r.mode) {
584 .positional, .positional_reading => {
585 const size = r.getSize() catch {
586 r.mode = r.mode.toStreaming();
587 return 0;
588 };
589 const logical_pos = logicalPos(r);
590 const delta = @min(@intFromEnum(limit), size - logical_pos);
591 setLogicalPos(r, logical_pos + delta);
592 return delta;
593 },
594 .streaming, .streaming_reading => {
595 // Unfortunately we can't seek forward without knowing the
596 // size because the seek syscalls provided to us will not
597 // return the true end position if a seek would exceed the
598 // end.
599 fallback: {
600 if (r.size_err == null and r.seek_err == null) break :fallback;
601
602 const buffered_len = r.interface.bufferedLen();
603 var remaining = @intFromEnum(limit);
604 if (remaining <= buffered_len) {
605 r.interface.seek += remaining;
606 return remaining;
607 }
608 remaining -= buffered_len;
609 r.interface.seek = 0;
610 r.interface.end = 0;
611
612 var trash_buffer: [128]u8 = undefined;
613 var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]};
614 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
615 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);
616 const dest = iovecs_buffer[0..dest_n];
617 assert(dest[0].len > 0);
618 const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| {
619 r.err = err;
620 return error.ReadFailed;
621 };
622 if (n == 0) {
623 r.size = r.pos;
624 return error.EndOfStream;
625 }
626 r.pos += n;
627 if (n > data_size) {
628 r.interface.end += n - data_size;
629 remaining -= data_size;
630 } else {
631 remaining -= n;
632 }
633 return @intFromEnum(limit) - remaining;
634 }
635 const size = r.getSize() catch return 0;
636 const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit));
637 io.vtable.fileSeekBy(io.userdata, file, n) catch |err| {
638 r.seek_err = err;
639 return 0;
640 };
641 r.pos += n;
642 return n;
643 },
644 .failure => return error.ReadFailed,
645 }
554/// Equivalent to creating a positional reader and reading multiple times to fill `buffer`.
555///
556/// Returns number of bytes read into `buffer`. If less than `buffer.len`, end of file occurred.
557///
558/// See also:
559/// * `reader`
560pub fn readPositionalAll(file: File, io: Io, buffer: []u8, offset: u64) ReadPositionalError!usize {
561 var index: usize = 0;
562 while (index != buffer.len) {
563 const amt = try file.readPositional(io, &.{buffer[index..]}, offset + index);
564 if (amt == 0) break;
565 index += amt;
646566 }
567 return index;
568}
569
570/// Positional is more threadsafe, since the global seek position is not
571/// affected, but when such syscalls are not available, preemptively
572/// initializing in streaming mode skips a failed syscall.
573///
574/// See also:
575/// * `reader`
576pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
577 return .initStreaming(file, io, buffer);
578}
647579
648 /// Returns whether the stream is at the logical end.
649 pub fn atEnd(r: *Reader) bool {
650 // Even if stat fails, size is set when end is encountered.
651 const size = r.size orelse return false;
652 return size - logicalPos(r) == 0;
580/// Defaults to positional reading; falls back to streaming.
581///
582/// Positional is more threadsafe, since the global seek position is not
583/// affected.
584pub fn writer(file: File, io: Io, buffer: []u8) Writer {
585 return .init(file, io, buffer);
586}
587
588/// Positional is more threadsafe, since the global seek position is not
589/// affected, but when such syscalls are not available, preemptively
590/// initializing in streaming mode will skip a failed syscall.
591pub fn writerStreaming(file: File, io: Io, buffer: []u8) Writer {
592 return .initStreaming(file, io, buffer);
593}
594
595/// Equivalent to creating a streaming writer, writing `bytes`, and then flushing.
596pub fn writeStreamingAll(file: File, io: Io, bytes: []const u8) Writer.Error!void {
597 var index: usize = 0;
598 while (index < bytes.len) {
599 index += try io.vtable.fileWriteStreaming(io.userdata, file, &.{}, &.{bytes[index..]}, 1);
653600 }
654};
601}
602
603pub const LockError = error{
604 SystemResources,
605 FileLocksUnsupported,
606} || Io.Cancelable || Io.UnexpectedError;
607
608/// Blocks when an incompatible lock is held by another process. A process may
609/// hold only one type of lock (shared or exclusive) on a file. When a process
610/// terminates in any way, the lock is released.
611///
612/// Assumes the file is unlocked.
613pub fn lock(file: File, io: Io, l: Lock) LockError!void {
614 return io.vtable.fileLock(io.userdata, file, l);
615}
616
617/// Assumes the file is locked.
618pub fn unlock(file: File, io: Io) void {
619 return io.vtable.fileUnlock(io.userdata, file);
620}
621
622/// Attempts to obtain a lock, returning `true` if the lock is obtained, and
623/// `false` if there was an existing incompatible lock held. A process may hold
624/// only one type of lock (shared or exclusive) on a file. When a process
625/// terminates in any way, the lock is released.
626///
627/// Assumes the file is unlocked.
628pub fn tryLock(file: File, io: Io, l: Lock) LockError!bool {
629 return io.vtable.fileTryLock(io.userdata, file, l);
630}
631
632pub const DowngradeLockError = Io.Cancelable || Io.UnexpectedError;
633
634/// Assumes the file is already locked in exclusive mode.
635/// Atomically modifies the lock to be in shared mode, without releasing it.
636pub fn downgradeLock(file: File, io: Io) LockError!void {
637 return io.vtable.fileDowngradeLock(io.userdata, file);
638}
639
640pub const RealPathError = error{
641 /// This operating system, file system, or `Io` implementation does not
642 /// support realpath operations.
643 OperationUnsupported,
644 /// The full file system path could not fit into the provided buffer, or
645 /// due to its length could not be obtained via realpath functions no
646 /// matter the buffer size provided.
647 NameTooLong,
648 FileNotFound,
649 AccessDenied,
650 PermissionDenied,
651 NotDir,
652 SymLinkLoop,
653 InputOutput,
654 FileTooBig,
655 IsDir,
656 ProcessFdQuotaExceeded,
657 SystemFdQuotaExceeded,
658 NoDevice,
659 SystemResources,
660 NoSpaceLeft,
661 FileSystem,
662 DeviceBusy,
663 SharingViolation,
664 PipeBusy,
665 /// On Windows, `\\server` or `\\server\share` was not found.
666 NetworkNotFound,
667 PathAlreadyExists,
668 /// On Windows, antivirus software is enabled by default. It can be
669 /// disabled, but Windows Update sometimes ignores the user's preference
670 /// and re-enables it. When enabled, antivirus software on Windows
671 /// intercepts file system operations and makes them significantly slower
672 /// in addition to possibly failing with this error code.
673 AntivirusInterference,
674 /// On Windows, the volume does not contain a recognized file system. File
675 /// system drivers might not be loaded, or the volume may be corrupt.
676 UnrecognizedVolume,
677} || Io.Cancelable || Io.UnexpectedError;
678
679/// Obtains the canonicalized absolute path name corresponding to an open file
680/// handle.
681///
682/// This function has limited platform support, and using it can lead to
683/// unnecessary failures and race conditions. It is generally advisable to
684/// avoid this function entirely.
685pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize {
686 return io.vtable.fileRealPath(io.userdata, file, out_buffer);
687}
688
689test {
690 _ = Reader;
691 _ = Writer;
692 _ = Atomic;
693}
lib/std/Io/File/Atomic.zig created+102
......@@ -0,0 +1,102 @@
1const Atomic = @This();
2
3const std = @import("../../std.zig");
4const Io = std.Io;
5const File = std.Io.File;
6const Dir = std.Io.Dir;
7const assert = std.debug.assert;
8
9file_writer: File.Writer,
10random_integer: u64,
11dest_basename: []const u8,
12file_open: bool,
13file_exists: bool,
14close_dir_on_deinit: bool,
15dir: Dir,
16
17pub const InitError = File.OpenError;
18
19/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
20pub fn init(
21 io: Io,
22 dest_basename: []const u8,
23 permissions: File.Permissions,
24 dir: Dir,
25 close_dir_on_deinit: bool,
26 write_buffer: []u8,
27) InitError!Atomic {
28 while (true) {
29 const random_integer = std.crypto.random.int(u64);
30 const tmp_sub_path = std.fmt.hex(random_integer);
31 const file = dir.createFile(io, &tmp_sub_path, .{
32 .permissions = permissions,
33 .exclusive = true,
34 }) catch |err| switch (err) {
35 error.PathAlreadyExists => continue,
36 else => |e| return e,
37 };
38 return .{
39 .file_writer = file.writer(io, write_buffer),
40 .random_integer = random_integer,
41 .dest_basename = dest_basename,
42 .file_open = true,
43 .file_exists = true,
44 .close_dir_on_deinit = close_dir_on_deinit,
45 .dir = dir,
46 };
47 }
48}
49
50/// Always call deinit, even after a successful finish().
51pub fn deinit(af: *Atomic) void {
52 const io = af.file_writer.io;
53
54 if (af.file_open) {
55 af.file_writer.file.close(io);
56 af.file_open = false;
57 }
58 if (af.file_exists) {
59 const tmp_sub_path = std.fmt.hex(af.random_integer);
60 af.dir.deleteFile(io, &tmp_sub_path) catch {};
61 af.file_exists = false;
62 }
63 if (af.close_dir_on_deinit) {
64 af.dir.close(io);
65 }
66 af.* = undefined;
67}
68
69pub const FlushError = File.Writer.Error;
70
71pub fn flush(af: *Atomic) FlushError!void {
72 af.file_writer.interface.flush() catch |err| switch (err) {
73 error.WriteFailed => return af.file_writer.err.?,
74 };
75}
76
77pub const RenameIntoPlaceError = Dir.RenameError;
78
79/// On Windows, this function introduces a period of time where some file
80/// system operations on the destination file will result in
81/// `error.AccessDenied`, including rename operations (such as the one used in
82/// this function).
83pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void {
84 const io = af.file_writer.io;
85
86 assert(af.file_exists);
87 if (af.file_open) {
88 af.file_writer.file.close(io);
89 af.file_open = false;
90 }
91 const tmp_sub_path = std.fmt.hex(af.random_integer);
92 try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io);
93 af.file_exists = false;
94}
95
96pub const FinishError = FlushError || RenameIntoPlaceError;
97
98/// Combination of `flush` followed by `renameIntoPlace`.
99pub fn finish(af: *Atomic) FinishError!void {
100 try af.flush();
101 try af.renameIntoPlace();
102}
lib/std/Io/File/Reader.zig created+394
......@@ -0,0 +1,394 @@
1//! Memoizes key information about a file handle such as:
2//! * The size from calling stat, or the error that occurred therein.
3//! * The current seek position.
4//! * The error that occurred when trying to seek.
5//! * Whether reading should be done positionally or streaming.
6//! * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`)
7//! versus plain variants (e.g. `read`).
8//!
9//! Fulfills the `Io.Reader` interface.
10const Reader = @This();
11
12const std = @import("../../std.zig");
13const Io = std.Io;
14const File = std.Io.File;
15const assert = std.debug.assert;
16
17io: Io,
18file: File,
19err: ?Error = null,
20mode: Mode = .positional,
21/// Tracks the true seek position in the file. To obtain the logical position,
22/// use `logicalPos`.
23pos: u64 = 0,
24size: ?u64 = null,
25size_err: ?SizeError = null,
26seek_err: ?SeekError = null,
27interface: Io.Reader,
28
29pub const Error = error{
30 InputOutput,
31 SystemResources,
32 IsDir,
33 BrokenPipe,
34 ConnectionResetByPeer,
35 Timeout,
36 /// In WASI, EBADF is mapped to this error because it is returned when
37 /// trying to read a directory file descriptor as if it were a file.
38 NotOpenForReading,
39 SocketUnconnected,
40 /// Non-blocking has been enabled, and reading from the file descriptor
41 /// would block.
42 WouldBlock,
43 /// In WASI, this error occurs when the file descriptor does
44 /// not hold the required rights to read from it.
45 AccessDenied,
46 /// Unable to read file due to lock. Depending on the `Io` implementation,
47 /// reading from a locked file may return this error, or may ignore the
48 /// lock.
49 LockViolation,
50} || Io.Cancelable || Io.UnexpectedError;
51
52pub const SizeError = std.os.windows.GetFileSizeError || File.StatError || error{
53 /// Occurs if, for example, the file handle is a network socket and therefore does not have a size.
54 Streaming,
55};
56
57pub const SeekError = File.SeekError || error{
58 /// Seeking fell back to reading, and reached the end before the requested seek position.
59 /// `pos` remains at the end of the file.
60 EndOfStream,
61 /// Seeking fell back to reading, which failed.
62 ReadFailed,
63};
64
65pub const Mode = enum {
66 streaming,
67 positional,
68 /// Avoid syscalls other than `read` and `readv`.
69 streaming_simple,
70 /// Avoid syscalls other than `pread` and `preadv`.
71 positional_simple,
72 /// Indicates reading cannot continue because of a seek failure.
73 failure,
74
75 pub fn toStreaming(m: @This()) @This() {
76 return switch (m) {
77 .positional, .streaming => .streaming,
78 .positional_simple, .streaming_simple => .streaming_simple,
79 .failure => .failure,
80 };
81 }
82
83 pub fn toSimple(m: @This()) @This() {
84 return switch (m) {
85 .positional, .positional_simple => .positional_simple,
86 .streaming, .streaming_simple => .streaming_simple,
87 .failure => .failure,
88 };
89 }
90};
91
92pub fn initInterface(buffer: []u8) Io.Reader {
93 return .{
94 .vtable = &.{
95 .stream = stream,
96 .discard = discard,
97 .readVec = readVec,
98 },
99 .buffer = buffer,
100 .seek = 0,
101 .end = 0,
102 };
103}
104
105pub fn init(file: File, io: Io, buffer: []u8) Reader {
106 return .{
107 .io = io,
108 .file = file,
109 .interface = initInterface(buffer),
110 };
111}
112
113pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
114 return .{
115 .io = io,
116 .file = file,
117 .interface = initInterface(buffer),
118 .size = size,
119 };
120}
121
122/// Positional is more threadsafe, since the global seek position is not
123/// affected, but when such syscalls are not available, preemptively
124/// initializing in streaming mode skips a failed syscall.
125pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader {
126 return .{
127 .io = io,
128 .file = file,
129 .interface = Reader.initInterface(buffer),
130 .mode = .streaming,
131 .seek_err = error.Unseekable,
132 .size_err = error.Streaming,
133 };
134}
135
136pub fn getSize(r: *Reader) SizeError!u64 {
137 return r.size orelse {
138 if (r.size_err) |err| return err;
139 if (r.file.stat(r.io)) |st| {
140 if (st.kind == .file) {
141 r.size = st.size;
142 return st.size;
143 } else {
144 r.mode = r.mode.toStreaming();
145 r.size_err = error.Streaming;
146 return error.Streaming;
147 }
148 } else |err| {
149 r.size_err = err;
150 return err;
151 }
152 };
153}
154
155pub fn seekBy(r: *Reader, offset: i64) SeekError!void {
156 const io = r.io;
157 switch (r.mode) {
158 .positional, .positional_simple => {
159 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
160 },
161 .streaming, .streaming_simple => {
162 const seek_err = r.seek_err orelse e: {
163 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {
164 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
165 return;
166 } else |err| {
167 r.seek_err = err;
168 break :e err;
169 }
170 };
171 var remaining = std.math.cast(u64, offset) orelse return seek_err;
172 while (remaining > 0) {
173 remaining -= discard(&r.interface, .limited64(remaining)) catch |err| {
174 r.seek_err = err;
175 return err;
176 };
177 }
178 r.interface.tossBuffered();
179 },
180 .failure => return r.seek_err.?,
181 }
182}
183
184/// Repositions logical read offset relative to the beginning of the file.
185pub fn seekTo(r: *Reader, offset: u64) SeekError!void {
186 const io = r.io;
187 switch (r.mode) {
188 .positional, .positional_simple => {
189 setLogicalPos(r, offset);
190 },
191 .streaming, .streaming_simple => {
192 const logical_pos = logicalPos(r);
193 if (offset >= logical_pos) return seekBy(r, @intCast(offset - logical_pos));
194 if (r.seek_err) |err| return err;
195 io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
196 r.seek_err = err;
197 return err;
198 };
199 setLogicalPos(r, offset);
200 },
201 .failure => return r.seek_err.?,
202 }
203}
204
205pub fn logicalPos(r: *const Reader) u64 {
206 return r.pos - r.interface.bufferedLen();
207}
208
209fn setLogicalPos(r: *Reader, offset: u64) void {
210 const logical_pos = r.logicalPos();
211 if (offset < logical_pos or offset >= r.pos) {
212 r.interface.tossBuffered();
213 r.pos = offset;
214 } else r.interface.toss(@intCast(offset - logical_pos));
215}
216
217/// Number of slices to store on the stack, when trying to send as many byte
218/// vectors through the underlying read calls as possible.
219const max_buffers_len = 16;
220
221fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
222 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
223 return streamMode(r, w, limit, r.mode);
224}
225
226pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Reader.StreamError!usize {
227 switch (mode) {
228 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
229 error.Unimplemented => {
230 r.mode = r.mode.toSimple();
231 return 0;
232 },
233 else => |e| return e,
234 },
235 .positional_simple => {
236 const dest = limit.slice(try w.writableSliceGreedy(1));
237 var data: [1][]u8 = .{dest};
238 const n = try readVecPositional(r, &data);
239 w.advance(n);
240 return n;
241 },
242 .streaming_simple => {
243 const dest = limit.slice(try w.writableSliceGreedy(1));
244 var data: [1][]u8 = .{dest};
245 const n = try readVecStreaming(r, &data);
246 w.advance(n);
247 return n;
248 },
249 .failure => return error.ReadFailed,
250 }
251}
252
253fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
254 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
255 switch (r.mode) {
256 .positional, .positional_simple => return readVecPositional(r, data),
257 .streaming, .streaming_simple => return readVecStreaming(r, data),
258 .failure => return error.ReadFailed,
259 }
260}
261
262fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
263 const io = r.io;
264 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
265 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
266 const dest = iovecs_buffer[0..dest_n];
267 assert(dest[0].len > 0);
268 const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) {
269 error.Unseekable => {
270 r.mode = r.mode.toStreaming();
271 const pos = r.pos;
272 if (pos != 0) {
273 r.pos = 0;
274 r.seekBy(@intCast(pos)) catch {
275 r.mode = .failure;
276 return error.ReadFailed;
277 };
278 }
279 return 0;
280 },
281 else => |e| {
282 r.err = e;
283 return error.ReadFailed;
284 },
285 };
286 if (n == 0) {
287 r.size = r.pos;
288 return error.EndOfStream;
289 }
290 r.pos += n;
291 if (n > data_size) {
292 r.interface.end += n - data_size;
293 return data_size;
294 }
295 return n;
296}
297
298fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
299 const io = r.io;
300 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
301 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
302 const dest = iovecs_buffer[0..dest_n];
303 assert(dest[0].len > 0);
304 const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| {
305 r.err = err;
306 return error.ReadFailed;
307 };
308 if (n == 0) {
309 r.size = r.pos;
310 return error.EndOfStream;
311 }
312 r.pos += n;
313 if (n > data_size) {
314 r.interface.end += n - data_size;
315 return data_size;
316 }
317 return n;
318}
319
320fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
321 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
322 const io = r.io;
323 const file = r.file;
324 switch (r.mode) {
325 .positional, .positional_simple => {
326 const size = r.getSize() catch {
327 r.mode = r.mode.toStreaming();
328 return 0;
329 };
330 const logical_pos = logicalPos(r);
331 const delta = @min(@intFromEnum(limit), size - logical_pos);
332 setLogicalPos(r, logical_pos + delta);
333 return delta;
334 },
335 .streaming, .streaming_simple => {
336 // Unfortunately we can't seek forward without knowing the
337 // size because the seek syscalls provided to us will not
338 // return the true end position if a seek would exceed the
339 // end.
340 fallback: {
341 if (r.size_err == null and r.seek_err == null) break :fallback;
342
343 const buffered_len = r.interface.bufferedLen();
344 var remaining = @intFromEnum(limit);
345 if (remaining <= buffered_len) {
346 r.interface.seek += remaining;
347 return remaining;
348 }
349 remaining -= buffered_len;
350 r.interface.seek = 0;
351 r.interface.end = 0;
352
353 var trash_buffer: [128]u8 = undefined;
354 var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]};
355 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
356 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);
357 const dest = iovecs_buffer[0..dest_n];
358 assert(dest[0].len > 0);
359 const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| {
360 r.err = err;
361 return error.ReadFailed;
362 };
363 if (n == 0) {
364 r.size = r.pos;
365 return error.EndOfStream;
366 }
367 r.pos += n;
368 if (n > data_size) {
369 r.interface.end += n - data_size;
370 remaining -= data_size;
371 } else {
372 remaining -= n;
373 }
374 return @intFromEnum(limit) - remaining;
375 }
376 const size = r.getSize() catch return 0;
377 const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit));
378 io.vtable.fileSeekBy(io.userdata, file, n) catch |err| {
379 r.seek_err = err;
380 return 0;
381 };
382 r.pos += n;
383 return n;
384 },
385 .failure => return error.ReadFailed,
386 }
387}
388
389/// Returns whether the stream is at the logical end.
390pub fn atEnd(r: *Reader) bool {
391 // Even if stat fails, size is set when end is encountered.
392 const size = r.size orelse return false;
393 return size - logicalPos(r) == 0;
394}
lib/std/Io/File/Writer.zig created+274
......@@ -0,0 +1,274 @@
1const Writer = @This();
2const builtin = @import("builtin");
3const is_windows = builtin.os.tag == .windows;
4
5const std = @import("../../std.zig");
6const Io = std.Io;
7const File = std.Io.File;
8const assert = std.debug.assert;
9
10io: Io,
11file: File,
12err: ?Error = null,
13mode: Mode = .positional,
14/// Tracks the true seek position in the file. To obtain the logical position,
15/// use `logicalPos`.
16pos: u64 = 0,
17write_file_err: ?WriteFileError = null,
18seek_err: ?SeekError = null,
19interface: Io.Writer,
20
21pub const Mode = File.Reader.Mode;
22
23pub const Error = error{
24 DiskQuota,
25 FileTooBig,
26 InputOutput,
27 NoSpaceLeft,
28 DeviceBusy,
29 /// File descriptor does not hold the required rights to write to it.
30 AccessDenied,
31 PermissionDenied,
32 /// File is an unconnected socket, or closed its read end.
33 BrokenPipe,
34 /// Insufficient kernel memory to read from in_fd.
35 SystemResources,
36 NotOpenForWriting,
37 /// The process cannot access the file because another process has locked
38 /// a portion of the file. Windows-only.
39 LockViolation,
40 /// Non-blocking has been enabled and this operation would block.
41 WouldBlock,
42 /// This error occurs when a device gets disconnected before or mid-flush
43 /// while it's being written to - errno(6): No such device or address.
44 NoDevice,
45 FileBusy,
46} || Io.Cancelable || Io.UnexpectedError;
47
48pub const WriteFileError = Error || error{
49 /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd.
50 Unimplemented,
51 /// Can happen on FreeBSD when using copy_file_range.
52 CorruptedData,
53 EndOfStream,
54 ReadFailed,
55};
56
57pub const SeekError = Io.File.SeekError;
58
59pub fn init(file: File, io: Io, buffer: []u8) Writer {
60 return .{
61 .io = io,
62 .file = file,
63 .interface = initInterface(buffer),
64 .mode = .positional,
65 };
66}
67
68/// Positional is more threadsafe, since the global seek position is not
69/// affected, but when such syscalls are not available, preemptively
70/// initializing in streaming mode will skip a failed syscall.
71pub fn initStreaming(file: File, io: Io, buffer: []u8) Writer {
72 return .{
73 .io = io,
74 .file = file,
75 .interface = initInterface(buffer),
76 .mode = .streaming,
77 };
78}
79
80/// Detects if `file` is terminal and sets the mode accordingly.
81pub fn initDetect(file: File, io: Io, buffer: []u8) Io.Cancelable!Writer {
82 return .{
83 .io = io,
84 .file = file,
85 .interface = initInterface(buffer),
86 .mode = try .detect(io, file, true, .positional),
87 };
88}
89
90pub fn initInterface(buffer: []u8) Io.Writer {
91 return .{
92 .vtable = &.{
93 .drain = drain,
94 .sendFile = sendFile,
95 },
96 .buffer = buffer,
97 };
98}
99
100pub fn moveToReader(w: *Writer) File.Reader {
101 defer w.* = undefined;
102 return .{
103 .io = w.io,
104 .file = .{ .handle = w.file.handle },
105 .mode = w.mode,
106 .pos = w.pos,
107 .interface = File.Reader.initInterface(w.interface.buffer),
108 .seek_err = w.seek_err,
109 };
110}
111
112pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
113 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
114 switch (w.mode) {
115 .positional, .positional_simple => return drainPositional(w, data, splat),
116 .streaming, .streaming_simple => return drainStreaming(w, data, splat),
117 .failure => return error.WriteFailed,
118 }
119}
120
121fn drainPositional(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
122 const io = w.io;
123 const header = w.interface.buffered();
124 const n = io.vtable.fileWritePositional(io.userdata, w.file, header, data, splat, w.pos) catch |err| switch (err) {
125 error.Unseekable => {
126 w.mode = w.mode.toStreaming();
127 const pos = w.pos;
128 if (pos != 0) {
129 w.pos = 0;
130 w.seekTo(@intCast(pos)) catch {
131 w.mode = .failure;
132 return error.WriteFailed;
133 };
134 }
135 return 0;
136 },
137 else => |e| {
138 w.err = e;
139 return error.WriteFailed;
140 },
141 };
142 w.pos += n;
143 return w.interface.consume(n);
144}
145
146fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
147 const io = w.io;
148 const header = w.interface.buffered();
149 const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| {
150 w.err = err;
151 return error.WriteFailed;
152 };
153 w.pos += n;
154 return w.interface.consume(n);
155}
156
157pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
158 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
159 switch (w.mode) {
160 .positional => return sendFilePositional(w, file_reader, limit),
161 .positional_simple => return error.Unimplemented,
162 .streaming => return sendFileStreaming(w, file_reader, limit),
163 .streaming_simple => return error.Unimplemented,
164 .failure => return error.WriteFailed,
165 }
166}
167
168fn sendFilePositional(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
169 const io = w.io;
170 const header = w.interface.buffered();
171 const n = io.vtable.fileWriteFilePositional(io.userdata, w.file, header, file_reader, limit, w.pos) catch |err| switch (err) {
172 error.Unseekable => {
173 w.mode = w.mode.toStreaming();
174 const pos = w.pos;
175 if (pos != 0) {
176 w.pos = 0;
177 w.seekTo(@intCast(pos)) catch {
178 w.mode = .failure;
179 return error.WriteFailed;
180 };
181 }
182 return 0;
183 },
184 error.Canceled => {
185 w.err = error.Canceled;
186 return error.WriteFailed;
187 },
188 error.EndOfStream => return error.EndOfStream,
189 error.Unimplemented => return error.Unimplemented,
190 error.ReadFailed => return error.ReadFailed,
191 else => |e| {
192 w.write_file_err = e;
193 return error.WriteFailed;
194 },
195 };
196 w.pos += n;
197 return w.interface.consume(n);
198}
199
200fn sendFileStreaming(w: *Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
201 const io = w.io;
202 const header = w.interface.buffered();
203 const n = io.vtable.fileWriteFileStreaming(io.userdata, w.file, header, file_reader, limit) catch |err| switch (err) {
204 error.Canceled => {
205 w.err = error.Canceled;
206 return error.WriteFailed;
207 },
208 error.EndOfStream => return error.EndOfStream,
209 error.Unimplemented => return error.Unimplemented,
210 error.ReadFailed => return error.ReadFailed,
211 else => |e| {
212 w.write_file_err = e;
213 return error.WriteFailed;
214 },
215 };
216 w.pos += n;
217 return w.interface.consume(n);
218}
219
220pub fn seekTo(w: *Writer, offset: u64) (SeekError || Io.Writer.Error)!void {
221 try w.interface.flush();
222 try seekToUnbuffered(w, offset);
223}
224
225pub fn logicalPos(w: *const Writer) u64 {
226 return w.pos + w.interface.end;
227}
228
229/// Asserts that no data is currently buffered.
230pub fn seekToUnbuffered(w: *Writer, offset: u64) SeekError!void {
231 assert(w.interface.buffered().len == 0);
232 const io = w.io;
233 switch (w.mode) {
234 .positional, .positional_simple => {
235 w.pos = offset;
236 },
237 .streaming, .streaming_simple => {
238 if (w.seek_err) |err| return err;
239 io.vtable.fileSeekTo(io.userdata, w.file, offset) catch |err| {
240 w.seek_err = err;
241 return err;
242 };
243 w.pos = offset;
244 },
245 .failure => return w.seek_err.?,
246 }
247}
248
249pub const EndError = File.SetLengthError || Io.Writer.Error;
250
251/// Flushes any buffered data and sets the end position of the file.
252///
253/// If not overwriting existing contents, then calling `interface.flush`
254/// directly is sufficient.
255///
256/// Flush failure is handled by setting `err` so that it can be handled
257/// along with other write failures.
258pub fn end(w: *Writer) EndError!void {
259 const io = w.io;
260 try w.interface.flush();
261 switch (w.mode) {
262 .positional,
263 .positional_simple,
264 => w.file.setLength(io, w.pos) catch |err| switch (err) {
265 error.NonResizable => return,
266 else => |e| return e,
267 },
268
269 .streaming,
270 .streaming_simple,
271 .failure,
272 => {},
273 }
274}
lib/std/Io/IoUring.zig+2-2
......@@ -1093,7 +1093,7 @@ fn createFile(
10931093 .PERM => return error.PermissionDenied,
10941094 .EXIST => return error.PathAlreadyExists,
10951095 .BUSY => return error.DeviceBusy,
1096 .OPNOTSUPP => return error.FileLocksNotSupported,
1096 .OPNOTSUPP => return error.FileLocksUnsupported,
10971097 .AGAIN => return error.WouldBlock,
10981098 .TXTBSY => return error.FileBusy,
10991099 .NXIO => return error.NoDevice,
......@@ -1201,7 +1201,7 @@ fn fileOpen(
12011201 .PERM => return error.PermissionDenied,
12021202 .EXIST => return error.PathAlreadyExists,
12031203 .BUSY => return error.DeviceBusy,
1204 .OPNOTSUPP => return error.FileLocksNotSupported,
1204 .OPNOTSUPP => return error.FileLocksUnsupported,
12051205 .AGAIN => return error.WouldBlock,
12061206 .TXTBSY => return error.FileBusy,
12071207 .NXIO => return error.NoDevice,
lib/std/Io/Kqueue.zig+10-10
......@@ -869,11 +869,11 @@ pub fn io(k: *Kqueue) Io {
869869 .conditionWaitUncancelable = conditionWaitUncancelable,
870870 .conditionWake = conditionWake,
871871
872 .dirMake = dirMake,
873 .dirMakePath = dirMakePath,
874 .dirMakeOpenPath = dirMakeOpenPath,
872 .dirCreateDir = dirCreateDir,
873 .dirCreateDirPath = dirCreateDirPath,
874 .dirCreateDirPathOpen = dirCreateDirPathOpen,
875875 .dirStat = dirStat,
876 .dirStatPath = dirStatPath,
876 .dirStatFile = dirStatFile,
877877
878878 .fileStat = fileStat,
879879 .dirAccess = dirAccess,
......@@ -888,7 +888,7 @@ pub fn io(k: *Kqueue) Io {
888888 .fileReadPositional = fileReadPositional,
889889 .fileSeekBy = fileSeekBy,
890890 .fileSeekTo = fileSeekTo,
891 .openSelfExe = openSelfExe,
891 .openExecutable = openExecutable,
892892
893893 .now = now,
894894 .sleep = sleep,
......@@ -1114,7 +1114,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
11141114 k.yield(waiting_fiber, .reschedule);
11151115}
11161116
1117fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void {
1117fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.CreateDirError!void {
11181118 const k: *Kqueue = @ptrCast(@alignCast(userdata));
11191119 _ = k;
11201120 _ = dir;
......@@ -1122,7 +1122,7 @@ fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode
11221122 _ = mode;
11231123 @panic("TODO");
11241124}
1125fn dirMakePath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void {
1125fn dirCreateDirPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.CreateDirError!void {
11261126 const k: *Kqueue = @ptrCast(@alignCast(userdata));
11271127 _ = k;
11281128 _ = dir;
......@@ -1130,7 +1130,7 @@ fn dirMakePath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.
11301130 _ = mode;
11311131 @panic("TODO");
11321132}
1133fn dirMakeOpenPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.OpenOptions) Dir.MakeOpenPathError!Dir {
1133fn dirCreateDirPathOpen(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir {
11341134 const k: *Kqueue = @ptrCast(@alignCast(userdata));
11351135 _ = k;
11361136 _ = dir;
......@@ -1144,7 +1144,7 @@ fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
11441144 _ = dir;
11451145 @panic("TODO");
11461146}
1147fn dirStatPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.StatPathOptions) Dir.StatPathError!File.Stat {
1147fn dirStatFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.StatPathOptions) Dir.StatFileError!File.Stat {
11481148 const k: *Kqueue = @ptrCast(@alignCast(userdata));
11491149 _ = k;
11501150 _ = dir;
......@@ -1246,7 +1246,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, absolute_offset: u64) File.Seek
12461246 _ = absolute_offset;
12471247 @panic("TODO");
12481248}
1249fn openSelfExe(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenSelfExeError!File {
1249fn openExecutable(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenExecutableError!File {
12501250 const k: *Kqueue = @ptrCast(@alignCast(userdata));
12511251 _ = k;
12521252 _ = file;
lib/std/Io/Terminal.zig created+138
......@@ -0,0 +1,138 @@
1/// Abstraction for writing to a stream that might support terminal escape
2/// codes.
3const Terminal = @This();
4
5const builtin = @import("builtin");
6const is_windows = builtin.os.tag == .windows;
7
8const std = @import("std");
9const Io = std.Io;
10const File = std.Io.File;
11
12writer: *Io.Writer,
13mode: Mode,
14
15pub const Color = enum {
16 black,
17 red,
18 green,
19 yellow,
20 blue,
21 magenta,
22 cyan,
23 white,
24 bright_black,
25 bright_red,
26 bright_green,
27 bright_yellow,
28 bright_blue,
29 bright_magenta,
30 bright_cyan,
31 bright_white,
32 dim,
33 bold,
34 reset,
35};
36
37pub const Mode = union(enum) {
38 no_color,
39 escape_codes,
40 windows_api: WindowsApi,
41
42 pub const WindowsApi = if (!is_windows) noreturn else struct {
43 handle: File.Handle,
44 reset_attributes: u16,
45 };
46
47 /// Detect suitable TTY configuration options for the given file (commonly
48 /// stdout/stderr).
49 ///
50 /// Will attempt to enable ANSI escape code support if necessary/possible.
51 ///
52 /// * `NO_COLOR` indicates whether "NO_COLOR" environment variable is
53 /// present and non-empty.
54 /// * `CLICOLOR_FORCE` indicates whether "CLICOLOR_FORCE" environment
55 /// variable is present and non-empty.
56 pub fn detect(io: Io, file: File, NO_COLOR: bool, CLICOLOR_FORCE: bool) Io.Cancelable!Mode {
57 const force_color: ?bool = if (NO_COLOR) false else if (CLICOLOR_FORCE) true else null;
58 if (force_color == false) return .no_color;
59
60 if (file.enableAnsiEscapeCodes(io)) |_| {
61 return .escape_codes;
62 } else |err| switch (err) {
63 error.Canceled => return error.Canceled,
64 error.NotTerminalDevice, error.Unexpected => {},
65 }
66
67 if (is_windows and try file.isTty(io)) {
68 const windows = std.os.windows;
69 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
70 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != 0) {
71 return .{ .windows_api = .{
72 .handle = file.handle,
73 .reset_attributes = info.wAttributes,
74 } };
75 }
76 }
77 return if (force_color == true) .escape_codes else .no_color;
78 }
79};
80
81pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error;
82
83pub fn setColor(t: Terminal, color: Color) SetColorError!void {
84 switch (t.mode) {
85 .no_color => return,
86 .escape_codes => {
87 const color_string = switch (color) {
88 .black => "\x1b[30m",
89 .red => "\x1b[31m",
90 .green => "\x1b[32m",
91 .yellow => "\x1b[33m",
92 .blue => "\x1b[34m",
93 .magenta => "\x1b[35m",
94 .cyan => "\x1b[36m",
95 .white => "\x1b[37m",
96 .bright_black => "\x1b[90m",
97 .bright_red => "\x1b[91m",
98 .bright_green => "\x1b[92m",
99 .bright_yellow => "\x1b[93m",
100 .bright_blue => "\x1b[94m",
101 .bright_magenta => "\x1b[95m",
102 .bright_cyan => "\x1b[96m",
103 .bright_white => "\x1b[97m",
104 .bold => "\x1b[1m",
105 .dim => "\x1b[2m",
106 .reset => "\x1b[0m",
107 };
108 try t.writer.writeAll(color_string);
109 },
110 .windows_api => |wa| {
111 const windows = std.os.windows;
112 const attributes: windows.WORD = switch (color) {
113 .black => 0,
114 .red => windows.FOREGROUND_RED,
115 .green => windows.FOREGROUND_GREEN,
116 .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
117 .blue => windows.FOREGROUND_BLUE,
118 .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
119 .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
120 .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
121 .bright_black => windows.FOREGROUND_INTENSITY,
122 .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
123 .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
124 .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
125 .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
126 .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
127 .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
128 .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
129 // "dim" is not supported using basic character attributes, but let's still make it do *something*.
130 // This matches the old behavior of TTY.Color before the bright variants were added.
131 .dim => windows.FOREGROUND_INTENSITY,
132 .reset => wa.reset_attributes,
133 };
134 try t.writer.flush();
135 try windows.SetConsoleTextAttribute(wa.handle, attributes);
136 },
137 }
138}
lib/std/Io/Threaded.zig+6123-698
......@@ -3,19 +3,22 @@ const Threaded = @This();
33const builtin = @import("builtin");
44const native_os = builtin.os.tag;
55const is_windows = native_os == .windows;
6const windows = std.os.windows;
7const ws2_32 = std.os.windows.ws2_32;
6const is_darwin = native_os.isDarwin();
87const is_debug = builtin.mode == .Debug;
98
109const std = @import("../std.zig");
1110const Io = std.Io;
1211const net = std.Io.net;
12const File = std.Io.File;
13const Dir = std.Io.Dir;
1314const HostName = std.Io.net.HostName;
1415const IpAddress = std.Io.net.IpAddress;
1516const Allocator = std.mem.Allocator;
1617const Alignment = std.mem.Alignment;
1718const assert = std.debug.assert;
1819const posix = std.posix;
20const windows = std.os.windows;
21const ws2_32 = std.os.windows.ws2_32;
1922
2023/// Thread-safe.
2124allocator: Allocator,
......@@ -26,23 +29,7 @@ join_requested: bool = false,
2629stack_size: usize,
2730/// All threads are spawned detached; this is how we wait until they all exit.
2831wait_group: std.Thread.WaitGroup = .{},
29/// Maximum thread pool size (excluding main thread) when dispatching async
30/// tasks. Until this limit, calls to `Io.async` when all threads are busy will
31/// cause a new thread to be spawned and permanently added to the pool. After
32/// this limit, calls to `Io.async` when all threads are busy run the task
33/// immediately.
34///
35/// Defaults to a number equal to logical CPU cores.
36///
37/// Protected by `mutex` once the I/O instance is already in use. See
38/// `setAsyncLimit`.
3932async_limit: Io.Limit,
40/// Maximum thread pool size (excluding main thread) for dispatching concurrent
41/// tasks. Until this limit, calls to `Io.concurrent` will increase the thread
42/// pool size.
43///
44/// concurrent tasks. After this number, calls to `Io.concurrent` return
45/// `error.ConcurrencyUnavailable`.
4633concurrent_limit: Io.Limit = .unlimited,
4734/// Error from calling `std.Thread.getCpuCount` in `init`.
4835cpu_count_error: ?std.Thread.CpuCountError,
......@@ -52,17 +39,7 @@ cpu_count_error: ?std.Thread.CpuCountError,
5239busy_count: usize = 0,
5340main_thread: Thread,
5441pid: Pid = .unknown,
55/// When a cancel request is made, blocking syscalls can be unblocked by
56/// issuing a signal. However, if the signal arrives after the check and before
57/// the syscall instruction, it is missed.
58///
59/// This option solves the race condition by retrying the signal delivery
60/// until it is acknowledged, with an exponential backoff.
61///
62/// Unfortunately, trying again until the cancellation request is acknowledged
63/// has been observed to be relatively slow, and usually strong cancellation
64/// guarantees are not needed, so this defaults to off.
65robust_cancel: RobustCancel = .disabled,
42robust_cancel: RobustCancel,
6643
6744wsa: if (is_windows) Wsa else struct {} = .{},
6845
......@@ -70,6 +47,64 @@ have_signal_handler: bool,
7047old_sig_io: if (have_sig_io) posix.Sigaction else void,
7148old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,
7249
50use_sendfile: UseSendfile = .default,
51use_copy_file_range: UseCopyFileRange = .default,
52use_fcopyfile: UseFcopyfile = .default,
53use_fchmodat2: UseFchmodat2 = .default,
54
55stderr_writer: File.Writer = .{
56 .io = undefined,
57 .interface = Io.File.Writer.initInterface(&.{}),
58 .file = if (is_windows) undefined else .stderr(),
59 .mode = .streaming,
60},
61stderr_mode: Io.Terminal.Mode = .no_color,
62stderr_writer_initialized: bool = false,
63
64argv0: Argv0,
65environ: Environ,
66
67pub const Argv0 = switch (native_os) {
68 .openbsd, .haiku => struct {
69 value: ?[*:0]const u8 = null,
70 },
71 else => struct {},
72};
73
74pub const Environ = struct {
75 /// Unmodified data directly from the OS.
76 block: Block = &.{},
77 /// Protected by `mutex`. Determines whether the other fields have been
78 /// memoized based on `block`.
79 initialized: bool = false,
80 /// Protected by `mutex`. Memoized based on `block`. Tracks whether the
81 /// environment variables are present, ignoring their value.
82 exist: Exist = .{},
83 /// Protected by `mutex`. Memoized based on `block`.
84 string: String = .{},
85 /// Protected by `mutex`. Tracks the problem, if any, that occurred when
86 /// trying to scan environment variables.
87 ///
88 /// Errors are only possible on WASI.
89 err: ?Error = null,
90
91 pub const Error = Allocator.Error || Io.UnexpectedError;
92
93 pub const Block = []const [*:0]const u8;
94
95 pub const Exist = struct {
96 NO_COLOR: bool = false,
97 CLICOLOR_FORCE: bool = false,
98 };
99
100 pub const String = switch (native_os) {
101 .openbsd, .haiku => struct {
102 PATH: ?[:0]const u8 = null,
103 },
104 else => struct {},
105 };
106};
107
73108pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
74109 enabled,
75110 disabled,
......@@ -82,6 +117,42 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
82117 _,
83118} else enum(u0) { unknown = 0 };
84119
120pub const UseSendfile = if (have_sendfile) enum {
121 enabled,
122 disabled,
123 pub const default: UseSendfile = .enabled;
124} else enum {
125 disabled,
126 pub const default: UseSendfile = .disabled;
127};
128
129pub const UseCopyFileRange = if (have_copy_file_range) enum {
130 enabled,
131 disabled,
132 pub const default: UseCopyFileRange = .enabled;
133} else enum {
134 disabled,
135 pub const default: UseCopyFileRange = .disabled;
136};
137
138pub const UseFcopyfile = if (have_fcopyfile) enum {
139 enabled,
140 disabled,
141 pub const default: UseFcopyfile = .enabled;
142} else enum {
143 disabled,
144 pub const default: UseFcopyfile = .disabled;
145};
146
147pub const UseFchmodat2 = if (have_fchmodat2 and !have_fchmodat_flags) enum {
148 enabled,
149 disabled,
150 pub const default: UseFchmodat2 = .enabled;
151} else enum {
152 disabled,
153 pub const default: UseFchmodat2 = .disabled;
154};
155
85156const Thread = struct {
86157 /// The value that needs to be passed to pthread_kill or tgkill in order to
87158 /// send a signal.
......@@ -164,13 +235,6 @@ const Thread = struct {
164235 ) orelse return;
165236 }
166237
167 fn endSyscallCanceled(thread: *Thread) Io.Cancelable {
168 if (thread.current_closure) |closure| {
169 @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release);
170 }
171 return error.Canceled;
172 }
173
174238 fn currentSignalId() SignaleeId {
175239 return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId();
176240 }
......@@ -229,7 +293,7 @@ const Thread = struct {
229293 .INTR => {}, // caller's responsibility to retry
230294 .AGAIN => {}, // ptr.* != expect
231295 .INVAL => {}, // possibly timeout overflow
232 .TIMEDOUT => {}, // timeout
296 .TIMEDOUT => {},
233297 .FAULT => recoverableOsBugDetected(), // ptr was invalid
234298 else => recoverableOsBugDetected(),
235299 }
......@@ -308,18 +372,25 @@ const Thread = struct {
308372 else => unreachable,
309373 };
310374 },
311 else => @compileError("unimplemented: futexWait"),
375 else => if (std.Thread.use_pthreads) {
376 // TODO integrate the following function being called with robust cancelation.
377 return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) {
378 error.Timeout => {},
379 };
380 } else {
381 @compileError("unimplemented: futexWait");
382 },
312383 }
313384 }
314385
315386 fn futexWake(ptr: *const u32, max_waiters: u32) void {
316387 @branchHint(.cold);
388 assert(max_waiters != 0);
317389
318390 if (builtin.single_threaded) return; // nothing to wake up
319391
320392 if (builtin.cpu.arch.isWasm()) {
321393 comptime assert(builtin.cpu.has(.wasm, .atomics));
322 assert(max_waiters != 0);
323394 const woken_count = asm volatile (
324395 \\local.get %[ptr]
325396 \\local.get %[waiters]
......@@ -364,7 +435,6 @@ const Thread = struct {
364435 }
365436 },
366437 .windows => {
367 assert(max_waiters != 0);
368438 switch (max_waiters) {
369439 1 => windows.ntdll.RtlWakeAddressSingle(ptr),
370440 else => windows.ntdll.RtlWakeAddressAll(ptr),
......@@ -385,7 +455,11 @@ const Thread = struct {
385455 else => unreachable, // deadlock due to operating system bug
386456 }
387457 },
388 else => @compileError("unimplemented: futexWake"),
458 else => if (std.Thread.use_pthreads) {
459 return pthreads_futex.wake(ptr, max_waiters);
460 } else {
461 @compileError("unimplemented: futexWake");
462 },
389463 }
390464 }
391465};
......@@ -505,6 +579,47 @@ const Closure = struct {
505579 }
506580};
507581
582pub const InitOptions = struct {
583 /// Affects how many bytes are memory-mapped for threads.
584 stack_size: usize = std.Thread.SpawnConfig.default_stack_size,
585 /// Maximum thread pool size (excluding main thread) when dispatching async
586 /// tasks. Until this limit, calls to `Io.async` when all threads are busy will
587 /// cause a new thread to be spawned and permanently added to the pool. After
588 /// this limit, calls to `Io.async` when all threads are busy run the task
589 /// immediately.
590 ///
591 /// Defaults to a number equal to logical CPU cores.
592 ///
593 /// Protected by `Threaded.mutex` once the I/O instance is already in use. See
594 /// `setAsyncLimit`.
595 async_limit: ?Io.Limit = null,
596 /// Maximum thread pool size (excluding main thread) for dispatching concurrent
597 /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread
598 /// pool size.
599 ///
600 /// concurrent tasks. After this number, calls to `Io.concurrent` return
601 /// `error.ConcurrencyUnavailable`.
602 concurrent_limit: Io.Limit = .unlimited,
603 /// When a cancel request is made, blocking syscalls can be unblocked by
604 /// issuing a signal. However, if the signal arrives after the check and before
605 /// the syscall instruction, it is missed.
606 ///
607 /// This option solves the race condition by retrying the signal delivery
608 /// until it is acknowledged, with an exponential backoff.
609 ///
610 /// Unfortunately, trying again until the cancellation request is acknowledged
611 /// has been observed to be relatively slow, and usually strong cancellation
612 /// guarantees are not needed, so this defaults to off.
613 robust_cancel: RobustCancel = .disabled,
614 /// Affects the following operations:
615 /// * `processExecutablePath` on OpenBSD and Haiku.
616 argv0: Argv0 = .{},
617 /// Affects the following operations:
618 /// * `fileIsTty`
619 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").
620 environ: Environ = .{},
621};
622
508623/// Related:
509624/// * `init_single_threaded`
510625pub fn init(
......@@ -516,6 +631,7 @@ pub fn init(
516631 /// If these functions are avoided, then `Allocator.failing` may be passed
517632 /// here.
518633 gpa: Allocator,
634 options: InitOptions,
519635) Threaded {
520636 if (builtin.single_threaded) return .init_single_threaded;
521637
......@@ -523,8 +639,9 @@ pub fn init(
523639
524640 var t: Threaded = .{
525641 .allocator = gpa,
526 .stack_size = std.Thread.SpawnConfig.default_stack_size,
527 .async_limit = if (cpu_count) |n| .limited(n - 1) else |_| .nothing,
642 .stack_size = options.stack_size,
643 .async_limit = options.async_limit orelse if (cpu_count) |n| .limited(n - 1) else |_| .nothing,
644 .concurrent_limit = options.concurrent_limit,
528645 .cpu_count_error = if (cpu_count) |_| null else |e| e,
529646 .old_sig_io = undefined,
530647 .old_sig_pipe = undefined,
......@@ -532,8 +649,11 @@ pub fn init(
532649 .main_thread = .{
533650 .signal_id = Thread.currentSignalId(),
534651 .current_closure = null,
535 .cancel_protection = undefined,
652 .cancel_protection = .unblocked,
536653 },
654 .argv0 = options.argv0,
655 .environ = options.environ,
656 .robust_cancel = options.robust_cancel,
537657 };
538658
539659 if (posix.Sigaction != void) {
......@@ -570,10 +690,26 @@ pub const init_single_threaded: Threaded = .{
570690 .main_thread = .{
571691 .signal_id = undefined,
572692 .current_closure = null,
573 .cancel_protection = undefined,
693 .cancel_protection = .unblocked,
574694 },
695 .robust_cancel = .disabled,
696 .argv0 = .{},
697 .environ = .{},
575698};
576699
700var global_single_threaded_instance: Threaded = .init_single_threaded;
701
702/// In general, the application is responsible for choosing the `Io`
703/// implementation and library code should accept an `Io` parameter rather than
704/// accessing this declaration. Most code should avoid referencing this
705/// declaration entirely.
706///
707/// However, in some cases such as debugging, it is desirable to hardcode a
708/// reference to this `Io` implementation.
709///
710/// This instance does not support concurrency or cancelation.
711pub const global_single_threaded: *Threaded = &global_single_threaded_instance;
712
577713pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {
578714 t.mutex.lock();
579715 defer t.mutex.unlock();
......@@ -607,7 +743,7 @@ fn worker(t: *Threaded) void {
607743 var thread: Thread = .{
608744 .signal_id = Thread.currentSignalId(),
609745 .current_closure = null,
610 .cancel_protection = undefined,
746 .cancel_protection = .unblocked,
611747 };
612748 Thread.current = &thread;
613749
......@@ -652,25 +788,64 @@ pub fn io(t: *Threaded) Io {
652788 .futexWaitUncancelable = futexWaitUncancelable,
653789 .futexWake = futexWake,
654790
655 .dirMake = dirMake,
656 .dirMakePath = dirMakePath,
657 .dirMakeOpenPath = dirMakeOpenPath,
791 .dirCreateDir = dirCreateDir,
792 .dirCreateDirPath = dirCreateDirPath,
793 .dirCreateDirPathOpen = dirCreateDirPathOpen,
658794 .dirStat = dirStat,
659 .dirStatPath = dirStatPath,
660 .fileStat = fileStat,
795 .dirStatFile = dirStatFile,
661796 .dirAccess = dirAccess,
662797 .dirCreateFile = dirCreateFile,
663798 .dirOpenFile = dirOpenFile,
664799 .dirOpenDir = dirOpenDir,
665800 .dirClose = dirClose,
801 .dirRead = dirRead,
802 .dirRealPath = dirRealPath,
803 .dirRealPathFile = dirRealPathFile,
804 .dirDeleteFile = dirDeleteFile,
805 .dirDeleteDir = dirDeleteDir,
806 .dirRename = dirRename,
807 .dirSymLink = dirSymLink,
808 .dirReadLink = dirReadLink,
809 .dirSetOwner = dirSetOwner,
810 .dirSetFileOwner = dirSetFileOwner,
811 .dirSetPermissions = dirSetPermissions,
812 .dirSetFilePermissions = dirSetFilePermissions,
813 .dirSetTimestamps = dirSetTimestamps,
814 .dirSetTimestampsNow = dirSetTimestampsNow,
815 .dirHardLink = dirHardLink,
816
817 .fileStat = fileStat,
818 .fileLength = fileLength,
666819 .fileClose = fileClose,
667820 .fileWriteStreaming = fileWriteStreaming,
668821 .fileWritePositional = fileWritePositional,
822 .fileWriteFileStreaming = fileWriteFileStreaming,
823 .fileWriteFilePositional = fileWriteFilePositional,
669824 .fileReadStreaming = fileReadStreaming,
670825 .fileReadPositional = fileReadPositional,
671826 .fileSeekBy = fileSeekBy,
672827 .fileSeekTo = fileSeekTo,
673 .openSelfExe = openSelfExe,
828 .fileSync = fileSync,
829 .fileIsTty = fileIsTty,
830 .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes,
831 .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes,
832 .fileSetLength = fileSetLength,
833 .fileSetOwner = fileSetOwner,
834 .fileSetPermissions = fileSetPermissions,
835 .fileSetTimestamps = fileSetTimestamps,
836 .fileSetTimestampsNow = fileSetTimestampsNow,
837 .fileLock = fileLock,
838 .fileTryLock = fileTryLock,
839 .fileUnlock = fileUnlock,
840 .fileDowngradeLock = fileDowngradeLock,
841 .fileRealPath = fileRealPath,
842
843 .processExecutableOpen = processExecutableOpen,
844 .processExecutablePath = processExecutablePath,
845 .lockStderr = lockStderr,
846 .tryLockStderr = tryLockStderr,
847 .unlockStderr = unlockStderr,
848 .processSetCurrentDir = processSetCurrentDir,
674849
675850 .now = now,
676851 .sleep = sleep,
......@@ -708,6 +883,7 @@ pub fn io(t: *Threaded) Io {
708883 .windows => netWriteWindows,
709884 else => netWritePosix,
710885 },
886 .netWriteFile = netWriteFile,
711887 .netSend = switch (native_os) {
712888 .windows => netSendWindows,
713889 else => netSendPosix,
......@@ -748,25 +924,64 @@ pub fn ioBasic(t: *Threaded) Io {
748924 .futexWaitUncancelable = futexWaitUncancelable,
749925 .futexWake = futexWake,
750926
751 .dirMake = dirMake,
752 .dirMakePath = dirMakePath,
753 .dirMakeOpenPath = dirMakeOpenPath,
927 .dirCreateDir = dirCreateDir,
928 .dirCreateDirPath = dirCreateDirPath,
929 .dirCreateDirPathOpen = dirCreateDirPathOpen,
754930 .dirStat = dirStat,
755 .dirStatPath = dirStatPath,
756 .fileStat = fileStat,
931 .dirStatFile = dirStatFile,
757932 .dirAccess = dirAccess,
758933 .dirCreateFile = dirCreateFile,
759934 .dirOpenFile = dirOpenFile,
760935 .dirOpenDir = dirOpenDir,
761936 .dirClose = dirClose,
937 .dirRead = dirRead,
938 .dirRealPath = dirRealPath,
939 .dirRealPathFile = dirRealPathFile,
940 .dirDeleteFile = dirDeleteFile,
941 .dirDeleteDir = dirDeleteDir,
942 .dirRename = dirRename,
943 .dirSymLink = dirSymLink,
944 .dirReadLink = dirReadLink,
945 .dirSetOwner = dirSetOwner,
946 .dirSetFileOwner = dirSetFileOwner,
947 .dirSetPermissions = dirSetPermissions,
948 .dirSetFilePermissions = dirSetFilePermissions,
949 .dirSetTimestamps = dirSetTimestamps,
950 .dirSetTimestampsNow = dirSetTimestampsNow,
951 .dirHardLink = dirHardLink,
952
953 .fileStat = fileStat,
954 .fileLength = fileLength,
762955 .fileClose = fileClose,
763956 .fileWriteStreaming = fileWriteStreaming,
764957 .fileWritePositional = fileWritePositional,
958 .fileWriteFileStreaming = fileWriteFileStreaming,
959 .fileWriteFilePositional = fileWriteFilePositional,
765960 .fileReadStreaming = fileReadStreaming,
766961 .fileReadPositional = fileReadPositional,
767962 .fileSeekBy = fileSeekBy,
768963 .fileSeekTo = fileSeekTo,
769 .openSelfExe = openSelfExe,
964 .fileSync = fileSync,
965 .fileIsTty = fileIsTty,
966 .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes,
967 .fileSupportsAnsiEscapeCodes = fileSupportsAnsiEscapeCodes,
968 .fileSetLength = fileSetLength,
969 .fileSetOwner = fileSetOwner,
970 .fileSetPermissions = fileSetPermissions,
971 .fileSetTimestamps = fileSetTimestamps,
972 .fileSetTimestampsNow = fileSetTimestampsNow,
973 .fileLock = fileLock,
974 .fileTryLock = fileTryLock,
975 .fileUnlock = fileUnlock,
976 .fileDowngradeLock = fileDowngradeLock,
977 .fileRealPath = fileRealPath,
978
979 .processExecutableOpen = processExecutableOpen,
980 .processExecutablePath = processExecutablePath,
981 .lockStderr = lockStderr,
982 .tryLockStderr = tryLockStderr,
983 .unlockStderr = unlockStderr,
984 .processSetCurrentDir = processSetCurrentDir,
770985
771986 .now = now,
772987 .sleep = sleep,
......@@ -780,6 +995,7 @@ pub fn ioBasic(t: *Threaded) Io {
780995 .netClose = netCloseUnavailable,
781996 .netRead = netReadUnavailable,
782997 .netWrite = netWriteUnavailable,
998 .netWriteFile = netWriteFileUnavailable,
783999 .netSend = netSendUnavailable,
7841000 .netReceive = netReceiveUnavailable,
7851001 .netInterfaceNameResolve = netInterfaceNameResolveUnavailable,
......@@ -789,7 +1005,7 @@ pub fn ioBasic(t: *Threaded) Io {
7891005 };
7901006}
7911007
792pub const socket_flags_unsupported = native_os.isDarwin() or native_os == .haiku;
1008pub const socket_flags_unsupported = is_darwin or native_os == .haiku;
7931009const have_accept4 = !socket_flags_unsupported;
7941010const have_flock_open_flags = @hasField(posix.O, "EXLOCK");
7951011const have_networking = native_os != .wasi;
......@@ -805,12 +1021,47 @@ const have_preadv = switch (native_os) {
8051021};
8061022const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO");
8071023const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE");
1024const have_sendfile = if (builtin.link_libc) @TypeOf(std.c.sendfile) != void else native_os == .linux;
1025const have_copy_file_range = switch (native_os) {
1026 .linux, .freebsd => true,
1027 else => false,
1028};
1029const have_fcopyfile = is_darwin;
1030const have_fchmodat2 = native_os == .linux and
1031 (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and
1032 (builtin.abi.isAndroid() or !std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 }));
1033const have_fchmodat_flags = native_os != .linux or
1034 (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 }));
1035
1036const have_fchown = switch (native_os) {
1037 .wasi, .windows => false,
1038 else => true,
1039};
1040
1041const have_fchmod = switch (native_os) {
1042 .windows => false,
1043 .wasi => builtin.link_libc,
1044 else => true,
1045};
8081046
8091047const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
8101048const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
8111049const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
8121050const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
8131051const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
1052const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate;
1053const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev;
1054const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile;
1055const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
1056 .major = 34,
1057 .minor = 0,
1058 .patch = 0,
1059} else .{
1060 .major = 2,
1061 .minor = 27,
1062 .patch = 0,
1063});
1064const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux;
8141065
8151066/// Trailing data:
8161067/// 1. context
......@@ -1018,7 +1269,6 @@ const GroupClosure = struct {
10181269 const group = gc.group;
10191270 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
10201271 const event: *Io.Event = @ptrCast(&group.context);
1021
10221272 current_thread.current_closure = closure;
10231273 current_thread.cancel_protection = .unblocked;
10241274
......@@ -1304,6 +1554,7 @@ fn cancel(
13041554}
13051555
13061556fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void {
1557 if (builtin.single_threaded) unreachable; // Deadlock.
13071558 const t: *Threaded = @ptrCast(@alignCast(userdata));
13081559 const current_thread = Thread.getCurrent(t);
13091560 const t_io = ioBasic(t);
......@@ -1311,37 +1562,30 @@ fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.
13111562 const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null;
13121563 break :ns std.math.lossyCast(u64, d.raw.toNanoseconds());
13131564 };
1314 switch (native_os) {
1315 .illumos, .netbsd, .openbsd => @panic("TODO"),
1316 else => try current_thread.futexWaitTimed(ptr, expected, timeout_ns),
1317 }
1565 return Thread.futexWaitTimed(current_thread, ptr, expected, timeout_ns);
13181566}
13191567
13201568fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {
1569 if (builtin.single_threaded) unreachable; // Deadlock.
13211570 const t: *Threaded = @ptrCast(@alignCast(userdata));
13221571 _ = t;
1323 switch (native_os) {
1324 .illumos, .netbsd, .openbsd => @panic("TODO"),
1325 else => Thread.futexWaitUncancelable(ptr, expected),
1326 }
1572 Thread.futexWaitUncancelable(ptr, expected);
13271573}
13281574
13291575fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
1576 if (builtin.single_threaded) unreachable; // Nothing to wake up.
13301577 const t: *Threaded = @ptrCast(@alignCast(userdata));
13311578 _ = t;
1332 switch (native_os) {
1333 .illumos, .netbsd, .openbsd => @panic("TODO"),
1334 else => Thread.futexWake(ptr, max_waiters),
1335 }
1579 Thread.futexWake(ptr, max_waiters);
13361580}
13371581
1338const dirMake = switch (native_os) {
1339 .windows => dirMakeWindows,
1340 .wasi => dirMakeWasi,
1341 else => dirMakePosix,
1582const dirCreateDir = switch (native_os) {
1583 .windows => dirCreateDirWindows,
1584 .wasi => dirCreateDirWasi,
1585 else => dirCreateDirPosix,
13421586};
13431587
1344fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1588fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
13451589 const t: *Threaded = @ptrCast(@alignCast(userdata));
13461590 const current_thread = Thread.getCurrent(t);
13471591
......@@ -1350,7 +1594,7 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
13501594
13511595 try current_thread.beginSyscall();
13521596 while (true) {
1353 switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, mode))) {
1597 switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, permissions.toMode()))) {
13541598 .SUCCESS => {
13551599 current_thread.endSyscall();
13561600 return;
......@@ -1359,7 +1603,6 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
13591603 try current_thread.checkCancel();
13601604 continue;
13611605 },
1362 .CANCELED => return current_thread.endSyscallCanceled(),
13631606 else => |e| {
13641607 current_thread.endSyscall();
13651608 switch (e) {
......@@ -1387,8 +1630,8 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
13871630 }
13881631}
13891632
1390fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1391 if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, mode);
1633fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
1634 if (builtin.link_libc) return dirCreateDirPosix(userdata, dir, sub_path, permissions);
13921635 const t: *Threaded = @ptrCast(@alignCast(userdata));
13931636 const current_thread = Thread.getCurrent(t);
13941637 try current_thread.beginSyscall();
......@@ -1402,7 +1645,6 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I
14021645 try current_thread.checkCancel();
14031646 continue;
14041647 },
1405 .CANCELED => return current_thread.endSyscallCanceled(),
14061648 else => |e| {
14071649 current_thread.endSyscall();
14081650 switch (e) {
......@@ -1429,13 +1671,13 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I
14291671 }
14301672}
14311673
1432fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1674fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
14331675 const t: *Threaded = @ptrCast(@alignCast(userdata));
14341676 const current_thread = Thread.getCurrent(t);
14351677 try current_thread.checkCancel();
14361678
14371679 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
1438 _ = mode;
1680 _ = permissions; // TODO use this value
14391681 const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{
14401682 .dir = dir.handle,
14411683 .access_mask = .{
......@@ -1455,62 +1697,75 @@ fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode
14551697 windows.CloseHandle(sub_dir_handle);
14561698}
14571699
1458const dirMakePath = switch (native_os) {
1459 .windows => dirMakePathWindows,
1460 else => dirMakePathPosix,
1461};
1462
1463fn dirMakePathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1700fn dirCreateDirPath(
1701 userdata: ?*anyopaque,
1702 dir: Dir,
1703 sub_path: []const u8,
1704 permissions: Dir.Permissions,
1705) Dir.CreateDirPathError!Dir.CreatePathStatus {
14641706 const t: *Threaded = @ptrCast(@alignCast(userdata));
1465 _ = t;
1466 _ = dir;
1467 _ = sub_path;
1468 _ = mode;
1469 @panic("TODO implement dirMakePathPosix");
1470}
14711707
1472fn dirMakePathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1473 const t: *Threaded = @ptrCast(@alignCast(userdata));
1474 _ = t;
1475 _ = dir;
1476 _ = sub_path;
1477 _ = mode;
1478 @panic("TODO implement dirMakePathWindows");
1708 var it = std.fs.path.componentIterator(sub_path);
1709 var status: Dir.CreatePathStatus = .existed;
1710 var component = it.last() orelse return error.BadPathName;
1711 while (true) {
1712 if (dirCreateDir(t, dir, component.path, permissions)) |_| {
1713 status = .created;
1714 } else |err| switch (err) {
1715 error.PathAlreadyExists => {
1716 // stat the file and return an error if it's not a directory
1717 // this is important because otherwise a dangling symlink
1718 // could cause an infinite loop
1719 const fstat = try dirStatFile(t, dir, component.path, .{});
1720 if (fstat.kind != .directory) return error.NotDir;
1721 },
1722 error.FileNotFound => |e| {
1723 component = it.previous() orelse return e;
1724 continue;
1725 },
1726 else => |e| return e,
1727 }
1728 component = it.next() orelse return status;
1729 }
14791730}
14801731
1481const dirMakeOpenPath = switch (native_os) {
1482 .windows => dirMakeOpenPathWindows,
1483 .wasi => dirMakeOpenPathWasi,
1484 else => dirMakeOpenPathPosix,
1732const dirCreateDirPathOpen = switch (native_os) {
1733 .windows => dirCreateDirPathOpenWindows,
1734 .wasi => dirCreateDirPathOpenWasi,
1735 else => dirCreateDirPathOpenPosix,
14851736};
14861737
1487fn dirMakeOpenPathPosix(
1738fn dirCreateDirPathOpenPosix(
14881739 userdata: ?*anyopaque,
1489 dir: Io.Dir,
1740 dir: Dir,
14901741 sub_path: []const u8,
1491 options: Io.Dir.OpenOptions,
1492) Io.Dir.MakeOpenPathError!Io.Dir {
1742 permissions: Dir.Permissions,
1743 options: Dir.OpenOptions,
1744) Dir.CreateDirPathOpenError!Dir {
14931745 const t: *Threaded = @ptrCast(@alignCast(userdata));
14941746 const t_io = ioBasic(t);
14951747 return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) {
14961748 error.FileNotFound => {
1497 try dir.makePath(t_io, sub_path);
1749 _ = try dir.createDirPathStatus(t_io, sub_path, permissions);
14981750 return dirOpenDirPosix(t, dir, sub_path, options);
14991751 },
15001752 else => |e| return e,
15011753 };
15021754}
15031755
1504fn dirMakeOpenPathWindows(
1756fn dirCreateDirPathOpenWindows(
15051757 userdata: ?*anyopaque,
1506 dir: Io.Dir,
1758 dir: Dir,
15071759 sub_path: []const u8,
1508 options: Io.Dir.OpenOptions,
1509) Io.Dir.MakeOpenPathError!Io.Dir {
1760 permissions: Dir.Permissions,
1761 options: Dir.OpenOptions,
1762) Dir.CreateDirPathOpenError!Dir {
15101763 const t: *Threaded = @ptrCast(@alignCast(userdata));
15111764 const current_thread = Thread.getCurrent(t);
15121765 const w = windows;
15131766
1767 _ = permissions; // TODO apply these permissions
1768
15141769 var it = std.fs.path.componentIterator(sub_path);
15151770 // If there are no components in the path, then create a dummy component with the full path.
15161771 var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{
......@@ -1526,7 +1781,7 @@ fn dirMakeOpenPathWindows(
15261781 const is_last = it.peekNext() == null;
15271782 const create_disposition: w.FILE.CREATE_DISPOSITION = if (is_last) .OPEN_IF else .CREATE;
15281783
1529 var result: Io.Dir = .{ .handle = undefined };
1784 var result: Dir = .{ .handle = undefined };
15301785
15311786 const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);
15321787 var nt_name: w.UNICODE_STRING = .{
......@@ -1584,16 +1839,10 @@ fn dirMakeOpenPathWindows(
15841839 // stat the file and return an error if it's not a directory
15851840 // this is important because otherwise a dangling symlink
15861841 // could cause an infinite loop
1587 check_dir: {
1588 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
1589 const fstat = dirStatPathWindows(t, dir, component.path, .{
1590 .follow_symlinks = options.follow_symlinks,
1591 }) catch |stat_err| switch (stat_err) {
1592 error.IsDir => break :check_dir,
1593 else => |e| return e,
1594 };
1595 if (fstat.kind != .directory) return error.NotDir;
1596 }
1842 const fstat = try dirStatFileWindows(t, dir, component.path, .{
1843 .follow_symlinks = options.follow_symlinks,
1844 });
1845 if (fstat.kind != .directory) return error.NotDir;
15971846
15981847 component = it.next().?;
15991848 continue;
......@@ -1616,46 +1865,51 @@ fn dirMakeOpenPathWindows(
16161865 }
16171866}
16181867
1619fn dirMakeOpenPathWasi(
1868fn dirCreateDirPathOpenWasi(
16201869 userdata: ?*anyopaque,
1621 dir: Io.Dir,
1870 dir: Dir,
16221871 sub_path: []const u8,
1623 options: Io.Dir.OpenOptions,
1624) Io.Dir.MakeOpenPathError!Io.Dir {
1872 permissions: Dir.Permissions,
1873 options: Dir.OpenOptions,
1874) Dir.CreateDirPathOpenError!Dir {
16251875 const t: *Threaded = @ptrCast(@alignCast(userdata));
16261876 const t_io = ioBasic(t);
16271877 return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) {
16281878 error.FileNotFound => {
1629 try dir.makePath(t_io, sub_path);
1879 _ = try dir.createDirPathStatus(t_io, sub_path, permissions);
16301880 return dirOpenDirWasi(t, dir, sub_path, options);
16311881 },
16321882 else => |e| return e,
16331883 };
16341884}
16351885
1636fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
1886fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
16371887 const t: *Threaded = @ptrCast(@alignCast(userdata));
1638 _ = t;
1639 _ = dir;
1640 @panic("TODO implement dirStat");
1888 const file: File = .{ .handle = dir.handle };
1889 return fileStat(t, file);
16411890}
16421891
1643const dirStatPath = switch (native_os) {
1644 .linux => dirStatPathLinux,
1645 .windows => dirStatPathWindows,
1646 .wasi => dirStatPathWasi,
1647 else => dirStatPathPosix,
1892const dirStatFile = switch (native_os) {
1893 .linux => dirStatFileLinux,
1894 .windows => dirStatFileWindows,
1895 .wasi => dirStatFileWasi,
1896 else => dirStatFilePosix,
16481897};
16491898
1650fn dirStatPathLinux(
1899fn dirStatFileLinux(
16511900 userdata: ?*anyopaque,
1652 dir: Io.Dir,
1901 dir: Dir,
16531902 sub_path: []const u8,
1654 options: Io.Dir.StatPathOptions,
1655) Io.Dir.StatPathError!Io.File.Stat {
1903 options: Dir.StatFileOptions,
1904) Dir.StatFileError!File.Stat {
16561905 const t: *Threaded = @ptrCast(@alignCast(userdata));
16571906 const current_thread = Thread.getCurrent(t);
16581907 const linux = std.os.linux;
1908 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
1909 .{ .major = 30, .minor = 0, .patch = 0 }
1910 else
1911 .{ .major = 2, .minor = 28, .patch = 0 });
1912 const sys = if (use_c) std.c else std.os.linux;
16591913
16601914 var path_buffer: [posix.PATH_MAX]u8 = undefined;
16611915 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
......@@ -1666,30 +1920,15 @@ fn dirStatPathLinux(
16661920 try current_thread.beginSyscall();
16671921 while (true) {
16681922 var statx = std.mem.zeroes(linux.Statx);
1669 const rc = linux.statx(
1670 dir.handle,
1671 sub_path_posix,
1672 flags,
1673 .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true },
1674 &statx,
1675 );
1676 switch (linux.errno(rc)) {
1923 switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_mask, &statx))) {
16771924 .SUCCESS => {
16781925 current_thread.endSyscall();
1679 assert(statx.mask.TYPE);
1680 assert(statx.mask.MODE);
1681 assert(statx.mask.ATIME);
1682 assert(statx.mask.MTIME);
1683 assert(statx.mask.CTIME);
1684 assert(statx.mask.INO);
1685 assert(statx.mask.SIZE);
16861926 return statFromLinux(&statx);
16871927 },
16881928 .INTR => {
16891929 try current_thread.checkCancel();
16901930 continue;
16911931 },
1692 .CANCELED => return current_thread.endSyscallCanceled(),
16931932 else => |e| {
16941933 current_thread.endSyscall();
16951934 switch (e) {
......@@ -1709,12 +1948,12 @@ fn dirStatPathLinux(
17091948 }
17101949}
17111950
1712fn dirStatPathPosix(
1951fn dirStatFilePosix(
17131952 userdata: ?*anyopaque,
1714 dir: Io.Dir,
1953 dir: Dir,
17151954 sub_path: []const u8,
1716 options: Io.Dir.StatPathOptions,
1717) Io.Dir.StatPathError!Io.File.Stat {
1955 options: Dir.StatFileOptions,
1956) Dir.StatFileError!File.Stat {
17181957 const t: *Threaded = @ptrCast(@alignCast(userdata));
17191958 const current_thread = Thread.getCurrent(t);
17201959
......@@ -1723,10 +1962,14 @@ fn dirStatPathPosix(
17231962
17241963 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
17251964
1965 return posixStatFile(current_thread, dir.handle, sub_path_posix, flags);
1966}
1967
1968fn posixStatFile(current_thread: *Thread, dir_fd: posix.fd_t, sub_path: [:0]const u8, flags: u32) Dir.StatFileError!File.Stat {
17261969 try current_thread.beginSyscall();
17271970 while (true) {
17281971 var stat = std.mem.zeroes(posix.Stat);
1729 switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) {
1972 switch (posix.errno(fstatat_sym(dir_fd, sub_path, &stat, flags))) {
17301973 .SUCCESS => {
17311974 current_thread.endSyscall();
17321975 return statFromPosix(&stat);
......@@ -1735,7 +1978,6 @@ fn dirStatPathPosix(
17351978 try current_thread.checkCancel();
17361979 continue;
17371980 },
1738 .CANCELED => return current_thread.endSyscallCanceled(),
17391981 else => |e| {
17401982 current_thread.endSyscall();
17411983 switch (e) {
......@@ -1757,12 +1999,12 @@ fn dirStatPathPosix(
17571999 }
17582000}
17592001
1760fn dirStatPathWindows(
2002fn dirStatFileWindows(
17612003 userdata: ?*anyopaque,
1762 dir: Io.Dir,
2004 dir: Dir,
17632005 sub_path: []const u8,
1764 options: Io.Dir.StatPathOptions,
1765) Io.Dir.StatPathError!Io.File.Stat {
2006 options: Dir.StatFileOptions,
2007) Dir.StatFileError!File.Stat {
17662008 const t: *Threaded = @ptrCast(@alignCast(userdata));
17672009 const file = try dirOpenFileWindows(t, dir, sub_path, .{
17682010 .follow_symlinks = options.follow_symlinks,
......@@ -1771,13 +2013,13 @@ fn dirStatPathWindows(
17712013 return fileStatWindows(t, file);
17722014}
17732015
1774fn dirStatPathWasi(
2016fn dirStatFileWasi(
17752017 userdata: ?*anyopaque,
1776 dir: Io.Dir,
2018 dir: Dir,
17772019 sub_path: []const u8,
1778 options: Io.Dir.StatPathOptions,
1779) Io.Dir.StatPathError!Io.File.Stat {
1780 if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options);
2020 options: Dir.StatFileOptions,
2021) Dir.StatFileError!File.Stat {
2022 if (builtin.link_libc) return dirStatFilePosix(userdata, dir, sub_path, options);
17812023 const t: *Threaded = @ptrCast(@alignCast(userdata));
17822024 const current_thread = Thread.getCurrent(t);
17832025 const wasi = std.os.wasi;
......@@ -1796,7 +2038,6 @@ fn dirStatPathWasi(
17962038 try current_thread.checkCancel();
17972039 continue;
17982040 },
1799 .CANCELED => return current_thread.endSyscallCanceled(),
18002041 else => |e| {
18012042 current_thread.endSyscall();
18022043 switch (e) {
......@@ -1817,6 +2058,51 @@ fn dirStatPathWasi(
18172058 }
18182059}
18192060
2061fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
2062 const t: *Threaded = @ptrCast(@alignCast(userdata));
2063
2064 if (native_os == .linux) {
2065 const current_thread = Thread.getCurrent(t);
2066 const linux = std.os.linux;
2067
2068 try current_thread.beginSyscall();
2069 while (true) {
2070 var statx = std.mem.zeroes(linux.Statx);
2071 switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .{ .SIZE = true }, &statx))) {
2072 .SUCCESS => {
2073 current_thread.endSyscall();
2074 if (!statx.mask.SIZE) return error.Unexpected;
2075 return statx.size;
2076 },
2077 .INTR => {
2078 try current_thread.checkCancel();
2079 continue;
2080 },
2081 else => |e| {
2082 current_thread.endSyscall();
2083 switch (e) {
2084 .ACCES => |err| return errnoBug(err),
2085 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2086 .FAULT => |err| return errnoBug(err),
2087 .INVAL => |err| return errnoBug(err),
2088 .LOOP => |err| return errnoBug(err),
2089 .NAMETOOLONG => |err| return errnoBug(err),
2090 .NOENT => |err| return errnoBug(err),
2091 .NOMEM => return error.SystemResources,
2092 .NOTDIR => |err| return errnoBug(err),
2093 else => |err| return posix.unexpectedErrno(err),
2094 }
2095 },
2096 }
2097 }
2098 } else if (is_windows) {
2099 // TODO call NtQueryInformationFile and ask for only the size instead of "all"
2100 }
2101
2102 const stat = try fileStat(t, file);
2103 return stat.size;
2104}
2105
18202106const fileStat = switch (native_os) {
18212107 .linux => fileStatLinux,
18222108 .windows => fileStatWindows,
......@@ -1824,7 +2110,7 @@ const fileStat = switch (native_os) {
18242110 else => fileStatPosix,
18252111};
18262112
1827fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
2113fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
18282114 const t: *Threaded = @ptrCast(@alignCast(userdata));
18292115 const current_thread = Thread.getCurrent(t);
18302116
......@@ -1842,7 +2128,6 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File
18422128 try current_thread.checkCancel();
18432129 continue;
18442130 },
1845 .CANCELED => return current_thread.endSyscallCanceled(),
18462131 else => |e| {
18472132 current_thread.endSyscall();
18482133 switch (e) {
......@@ -1857,38 +2142,28 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File
18572142 }
18582143}
18592144
1860fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
2145fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
18612146 const t: *Threaded = @ptrCast(@alignCast(userdata));
18622147 const current_thread = Thread.getCurrent(t);
18632148 const linux = std.os.linux;
2149 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
2150 .{ .major = 30, .minor = 0, .patch = 0 }
2151 else
2152 .{ .major = 2, .minor = 28, .patch = 0 });
2153 const sys = if (use_c) std.c else std.os.linux;
18642154
18652155 try current_thread.beginSyscall();
18662156 while (true) {
18672157 var statx = std.mem.zeroes(linux.Statx);
1868 const rc = linux.statx(
1869 file.handle,
1870 "",
1871 linux.AT.EMPTY_PATH,
1872 .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true },
1873 &statx,
1874 );
1875 switch (linux.errno(rc)) {
2158 switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_mask, &statx))) {
18762159 .SUCCESS => {
18772160 current_thread.endSyscall();
1878 assert(statx.mask.TYPE);
1879 assert(statx.mask.MODE);
1880 assert(statx.mask.ATIME);
1881 assert(statx.mask.MTIME);
1882 assert(statx.mask.CTIME);
1883 assert(statx.mask.INO);
1884 assert(statx.mask.SIZE);
18852161 return statFromLinux(&statx);
18862162 },
18872163 .INTR => {
18882164 try current_thread.checkCancel();
18892165 continue;
18902166 },
1891 .CANCELED => return current_thread.endSyscallCanceled(),
18922167 else => |e| {
18932168 current_thread.endSyscall();
18942169 switch (e) {
......@@ -1908,7 +2183,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File
19082183 }
19092184}
19102185
1911fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
2186fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
19122187 const t: *Threaded = @ptrCast(@alignCast(userdata));
19132188 const current_thread = Thread.getCurrent(t);
19142189 try current_thread.checkCancel();
......@@ -1922,14 +2197,14 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi
19222197 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
19232198 // (name, volume name, etc) we don't care about.
19242199 .BUFFER_OVERFLOW => {},
1925 .INVALID_PARAMETER => unreachable,
2200 .INVALID_PARAMETER => |err| return windows.statusBug(err),
19262201 .ACCESS_DENIED => return error.AccessDenied,
19272202 else => return windows.unexpectedStatus(rc),
19282203 }
19292204 return .{
19302205 .inode = info.InternalInformation.IndexNumber,
19312206 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
1932 .mode = 0,
2207 .permissions = .default_file,
19332208 .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: {
19342209 var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined;
19352210 const tag_rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), .AttributeTag);
......@@ -1937,7 +2212,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi
19372212 .SUCCESS => {},
19382213 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
19392214 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
1940 .INFO_LENGTH_MISMATCH => unreachable,
2215 .INFO_LENGTH_MISMATCH => |err| return windows.statusBug(err),
19412216 .ACCESS_DENIED => return error.AccessDenied,
19422217 else => return windows.unexpectedStatus(rc),
19432218 }
......@@ -1951,10 +2226,11 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi
19512226 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
19522227 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
19532228 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
2229 .nlink = 0,
19542230 };
19552231}
19562232
1957fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
2233fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
19582234 if (builtin.link_libc) return fileStatPosix(userdata, file);
19592235
19602236 const t: *Threaded = @ptrCast(@alignCast(userdata));
......@@ -1972,7 +2248,6 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.
19722248 try current_thread.checkCancel();
19732249 continue;
19742250 },
1975 .CANCELED => return current_thread.endSyscallCanceled(),
19762251 else => |e| {
19772252 current_thread.endSyscall();
19782253 switch (e) {
......@@ -1996,10 +2271,10 @@ const dirAccess = switch (native_os) {
19962271
19972272fn dirAccessPosix(
19982273 userdata: ?*anyopaque,
1999 dir: Io.Dir,
2274 dir: Dir,
20002275 sub_path: []const u8,
2001 options: Io.Dir.AccessOptions,
2002) Io.Dir.AccessError!void {
2276 options: Dir.AccessOptions,
2277) Dir.AccessError!void {
20032278 const t: *Threaded = @ptrCast(@alignCast(userdata));
20042279 const current_thread = Thread.getCurrent(t);
20052280
......@@ -2024,7 +2299,6 @@ fn dirAccessPosix(
20242299 try current_thread.checkCancel();
20252300 continue;
20262301 },
2027 .CANCELED => return current_thread.endSyscallCanceled(),
20282302 else => |e| {
20292303 current_thread.endSyscall();
20302304 switch (e) {
......@@ -2050,10 +2324,10 @@ fn dirAccessPosix(
20502324
20512325fn dirAccessWasi(
20522326 userdata: ?*anyopaque,
2053 dir: Io.Dir,
2327 dir: Dir,
20542328 sub_path: []const u8,
2055 options: Io.Dir.AccessOptions,
2056) Io.Dir.AccessError!void {
2329 options: Dir.AccessOptions,
2330) Dir.AccessError!void {
20572331 if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options);
20582332 const t: *Threaded = @ptrCast(@alignCast(userdata));
20592333 const current_thread = Thread.getCurrent(t);
......@@ -2074,7 +2348,6 @@ fn dirAccessWasi(
20742348 try current_thread.checkCancel();
20752349 continue;
20762350 },
2077 .CANCELED => return current_thread.endSyscallCanceled(),
20782351 else => |e| {
20792352 current_thread.endSyscall();
20802353 switch (e) {
......@@ -2123,10 +2396,10 @@ fn dirAccessWasi(
21232396
21242397fn dirAccessWindows(
21252398 userdata: ?*anyopaque,
2126 dir: Io.Dir,
2399 dir: Dir,
21272400 sub_path: []const u8,
2128 options: Io.Dir.AccessOptions,
2129) Io.Dir.AccessError!void {
2401 options: Dir.AccessOptions,
2402) Dir.AccessError!void {
21302403 const t: *Threaded = @ptrCast(@alignCast(userdata));
21312404 const current_thread = Thread.getCurrent(t);
21322405 try current_thread.checkCancel();
......@@ -2175,10 +2448,10 @@ const dirCreateFile = switch (native_os) {
21752448
21762449fn dirCreateFilePosix(
21772450 userdata: ?*anyopaque,
2178 dir: Io.Dir,
2451 dir: Dir,
21792452 sub_path: []const u8,
2180 flags: Io.File.CreateFlags,
2181) Io.File.OpenError!Io.File {
2453 flags: File.CreateFlags,
2454) File.OpenError!File {
21822455 const t: *Threaded = @ptrCast(@alignCast(userdata));
21832456 const current_thread = Thread.getCurrent(t);
21842457
......@@ -2211,7 +2484,7 @@ fn dirCreateFilePosix(
22112484
22122485 try current_thread.beginSyscall();
22132486 const fd: posix.fd_t = while (true) {
2214 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode);
2487 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.permissions.toMode());
22152488 switch (posix.errno(rc)) {
22162489 .SUCCESS => {
22172490 current_thread.endSyscall();
......@@ -2221,7 +2494,6 @@ fn dirCreateFilePosix(
22212494 try current_thread.checkCancel();
22222495 continue;
22232496 },
2224 .CANCELED => return current_thread.endSyscallCanceled(),
22252497 else => |e| {
22262498 current_thread.endSyscall();
22272499 switch (e) {
......@@ -2238,14 +2510,14 @@ fn dirCreateFilePosix(
22382510 .NFILE => return error.SystemFdQuotaExceeded,
22392511 .NODEV => return error.NoDevice,
22402512 .NOENT => return error.FileNotFound,
2241 .SRCH => return error.ProcessNotFound,
2513 .SRCH => return error.FileNotFound, // Linux when accessing procfs.
22422514 .NOMEM => return error.SystemResources,
22432515 .NOSPC => return error.NoSpaceLeft,
22442516 .NOTDIR => return error.NotDir,
22452517 .PERM => return error.PermissionDenied,
22462518 .EXIST => return error.PathAlreadyExists,
22472519 .BUSY => return error.DeviceBusy,
2248 .OPNOTSUPP => return error.FileLocksNotSupported,
2520 .OPNOTSUPP => return error.FileLocksUnsupported,
22492521 .AGAIN => return error.WouldBlock,
22502522 .TXTBSY => return error.FileBusy,
22512523 .NXIO => return error.NoDevice,
......@@ -2276,7 +2548,6 @@ fn dirCreateFilePosix(
22762548 try current_thread.checkCancel();
22772549 continue;
22782550 },
2279 .CANCELED => return current_thread.endSyscallCanceled(),
22802551 else => |e| {
22812552 current_thread.endSyscall();
22822553 switch (e) {
......@@ -2284,7 +2555,7 @@ fn dirCreateFilePosix(
22842555 .INVAL => |err| return errnoBug(err), // invalid parameters
22852556 .NOLCK => return error.SystemResources,
22862557 .AGAIN => return error.WouldBlock,
2287 .OPNOTSUPP => return error.FileLocksNotSupported,
2558 .OPNOTSUPP => return error.FileLocksUnsupported,
22882559 else => |err| return posix.unexpectedErrno(err),
22892560 }
22902561 },
......@@ -2338,10 +2609,10 @@ fn dirCreateFilePosix(
23382609
23392610fn dirCreateFileWindows(
23402611 userdata: ?*anyopaque,
2341 dir: Io.Dir,
2612 dir: Dir,
23422613 sub_path: []const u8,
2343 flags: Io.File.CreateFlags,
2344) Io.File.OpenError!Io.File {
2614 flags: File.CreateFlags,
2615) File.OpenError!File {
23452616 const w = windows;
23462617 const t: *Threaded = @ptrCast(@alignCast(userdata));
23472618 const current_thread = Thread.getCurrent(t);
......@@ -2367,35 +2638,42 @@ fn dirCreateFileWindows(
23672638 .OPEN_IF,
23682639 });
23692640 errdefer w.CloseHandle(handle);
2641
23702642 var io_status_block: w.IO_STATUS_BLOCK = undefined;
2371 const range_off: w.LARGE_INTEGER = 0;
2372 const range_len: w.LARGE_INTEGER = 1;
23732643 const exclusive = switch (flags.lock) {
23742644 .none => return .{ .handle = handle },
23752645 .shared => false,
23762646 .exclusive => true,
23772647 };
2378 try w.LockFile(
2648 const status = w.ntdll.NtLockFile(
23792649 handle,
23802650 null,
23812651 null,
23822652 null,
23832653 &io_status_block,
2384 &range_off,
2385 &range_len,
2654 &windows_lock_range_off,
2655 &windows_lock_range_len,
23862656 null,
23872657 @intFromBool(flags.lock_nonblocking),
23882658 @intFromBool(exclusive),
23892659 );
2660 switch (status) {
2661 .SUCCESS => {},
2662 .INSUFFICIENT_RESOURCES => return error.SystemResources,
2663 .LOCK_NOT_GRANTED => return error.WouldBlock,
2664 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
2665 else => return windows.unexpectedStatus(status),
2666 }
2667
23902668 return .{ .handle = handle };
23912669}
23922670
23932671fn dirCreateFileWasi(
23942672 userdata: ?*anyopaque,
2395 dir: Io.Dir,
2673 dir: Dir,
23962674 sub_path: []const u8,
2397 flags: Io.File.CreateFlags,
2398) Io.File.OpenError!Io.File {
2675 flags: File.CreateFlags,
2676) File.OpenError!File {
23992677 const t: *Threaded = @ptrCast(@alignCast(userdata));
24002678 const current_thread = Thread.getCurrent(t);
24012679 const wasi = std.os.wasi;
......@@ -2436,7 +2714,6 @@ fn dirCreateFileWasi(
24362714 try current_thread.checkCancel();
24372715 continue;
24382716 },
2439 .CANCELED => return current_thread.endSyscallCanceled(),
24402717 else => |e| {
24412718 current_thread.endSyscall();
24422719 switch (e) {
......@@ -2476,10 +2753,10 @@ const dirOpenFile = switch (native_os) {
24762753
24772754fn dirOpenFilePosix(
24782755 userdata: ?*anyopaque,
2479 dir: Io.Dir,
2756 dir: Dir,
24802757 sub_path: []const u8,
2481 flags: Io.File.OpenFlags,
2482) Io.File.OpenError!Io.File {
2758 flags: File.OpenFlags,
2759) File.OpenError!File {
24832760 const t: *Threaded = @ptrCast(@alignCast(userdata));
24842761 const current_thread = Thread.getCurrent(t);
24852762
......@@ -2490,6 +2767,7 @@ fn dirOpenFilePosix(
24902767 .wasi => .{
24912768 .read = flags.mode != .write_only,
24922769 .write = flags.mode != .read_only,
2770 .NOFOLLOW = !flags.follow_symlinks,
24932771 },
24942772 else => .{
24952773 .ACCMODE = switch (flags.mode) {
......@@ -2497,11 +2775,13 @@ fn dirOpenFilePosix(
24972775 .write_only => .WRONLY,
24982776 .read_write => .RDWR,
24992777 },
2778 .NOFOLLOW = !flags.follow_symlinks,
25002779 },
25012780 };
25022781 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
25032782 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
25042783 if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty;
2784 if (@hasField(posix.O, "PATH") and flags.path_only) os_flags.PATH = true;
25052785
25062786 // Use the O locking flags if the os supports them to acquire the lock
25072787 // atomically. Note that the NONBLOCK flag is removed after the openat()
......@@ -2530,7 +2810,6 @@ fn dirOpenFilePosix(
25302810 try current_thread.checkCancel();
25312811 continue;
25322812 },
2533 .CANCELED => return current_thread.endSyscallCanceled(),
25342813 else => |e| {
25352814 current_thread.endSyscall();
25362815 switch (e) {
......@@ -2547,14 +2826,14 @@ fn dirOpenFilePosix(
25472826 .NFILE => return error.SystemFdQuotaExceeded,
25482827 .NODEV => return error.NoDevice,
25492828 .NOENT => return error.FileNotFound,
2550 .SRCH => return error.ProcessNotFound,
2829 .SRCH => return error.FileNotFound, // Linux when opening procfs files.
25512830 .NOMEM => return error.SystemResources,
25522831 .NOSPC => return error.NoSpaceLeft,
25532832 .NOTDIR => return error.NotDir,
25542833 .PERM => return error.PermissionDenied,
25552834 .EXIST => return error.PathAlreadyExists,
25562835 .BUSY => return error.DeviceBusy,
2557 .OPNOTSUPP => return error.FileLocksNotSupported,
2836 .OPNOTSUPP => return error.FileLocksUnsupported,
25582837 .AGAIN => return error.WouldBlock,
25592838 .TXTBSY => return error.FileBusy,
25602839 .NXIO => return error.NoDevice,
......@@ -2566,6 +2845,18 @@ fn dirOpenFilePosix(
25662845 };
25672846 errdefer posix.close(fd);
25682847
2848 if (!flags.allow_directory) {
2849 const is_dir = is_dir: {
2850 const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) {
2851 // The directory-ness is either unknown or unknowable
2852 error.Streaming => break :is_dir false,
2853 else => |e| return e,
2854 };
2855 break :is_dir stat.kind == .directory;
2856 };
2857 if (is_dir) return error.IsDir;
2858 }
2859
25692860 if (have_flock and !have_flock_open_flags and flags.lock != .none) {
25702861 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
25712862 const lock_flags = switch (flags.lock) {
......@@ -2584,7 +2875,6 @@ fn dirOpenFilePosix(
25842875 try current_thread.checkCancel();
25852876 continue;
25862877 },
2587 .CANCELED => return current_thread.endSyscallCanceled(),
25882878 else => |e| {
25892879 current_thread.endSyscall();
25902880 switch (e) {
......@@ -2592,7 +2882,7 @@ fn dirOpenFilePosix(
25922882 .INVAL => |err| return errnoBug(err), // invalid parameters
25932883 .NOLCK => return error.SystemResources,
25942884 .AGAIN => return error.WouldBlock,
2595 .OPNOTSUPP => return error.FileLocksNotSupported,
2885 .OPNOTSUPP => return error.FileLocksUnsupported,
25962886 else => |err| return posix.unexpectedErrno(err),
25972887 }
25982888 },
......@@ -2613,7 +2903,6 @@ fn dirOpenFilePosix(
26132903 try current_thread.checkCancel();
26142904 continue;
26152905 },
2616 .CANCELED => return current_thread.endSyscallCanceled(),
26172906 else => |err| {
26182907 current_thread.endSyscall();
26192908 return posix.unexpectedErrno(err);
......@@ -2634,7 +2923,6 @@ fn dirOpenFilePosix(
26342923 try current_thread.checkCancel();
26352924 continue;
26362925 },
2637 .CANCELED => return current_thread.endSyscallCanceled(),
26382926 else => |err| {
26392927 current_thread.endSyscall();
26402928 return posix.unexpectedErrno(err);
......@@ -2648,10 +2936,10 @@ fn dirOpenFilePosix(
26482936
26492937fn dirOpenFileWindows(
26502938 userdata: ?*anyopaque,
2651 dir: Io.Dir,
2939 dir: Dir,
26522940 sub_path: []const u8,
2653 flags: Io.File.OpenFlags,
2654) Io.File.OpenError!Io.File {
2941 flags: File.OpenFlags,
2942) File.OpenError!File {
26552943 const t: *Threaded = @ptrCast(@alignCast(userdata));
26562944 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
26572945 const sub_path_w = sub_path_w_array.span();
......@@ -2663,10 +2951,11 @@ pub fn dirOpenFileWtf16(
26632951 t: *Threaded,
26642952 dir_handle: ?windows.HANDLE,
26652953 sub_path_w: [:0]const u16,
2666 flags: Io.File.OpenFlags,
2667) Io.File.OpenError!Io.File {
2668 if (std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;
2669 if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir;
2954 flags: File.OpenFlags,
2955) File.OpenError!File {
2956 const allow_directory = flags.allow_directory and !flags.isWrite();
2957 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;
2958 if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir;
26702959 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
26712960 const current_thread = Thread.getCurrent(t);
26722961 const w = windows;
......@@ -2711,7 +3000,7 @@ pub fn dirOpenFileWtf16(
27113000 .OPEN,
27123001 .{
27133002 .IO = if (flags.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS,
2714 .NON_DIRECTORY_FILE = true,
3003 .NON_DIRECTORY_FILE = !allow_directory,
27153004 .OPEN_REPARSE_POINT = !flags.follow_symlinks,
27163005 },
27173006 null,
......@@ -2763,34 +3052,39 @@ pub fn dirOpenFileWtf16(
27633052 };
27643053 errdefer w.CloseHandle(handle);
27653054
2766 const range_off: w.LARGE_INTEGER = 0;
2767 const range_len: w.LARGE_INTEGER = 1;
27683055 const exclusive = switch (flags.lock) {
27693056 .none => return .{ .handle = handle },
27703057 .shared => false,
27713058 .exclusive => true,
27723059 };
2773 try w.LockFile(
3060 const status = w.ntdll.NtLockFile(
27743061 handle,
27753062 null,
27763063 null,
27773064 null,
27783065 &io_status_block,
2779 &range_off,
2780 &range_len,
3066 &windows_lock_range_off,
3067 &windows_lock_range_len,
27813068 null,
27823069 @intFromBool(flags.lock_nonblocking),
27833070 @intFromBool(exclusive),
27843071 );
3072 switch (status) {
3073 .SUCCESS => {},
3074 .INSUFFICIENT_RESOURCES => return error.SystemResources,
3075 .LOCK_NOT_GRANTED => return error.WouldBlock,
3076 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
3077 else => return windows.unexpectedStatus(status),
3078 }
27853079 return .{ .handle = handle };
27863080}
27873081
27883082fn dirOpenFileWasi(
27893083 userdata: ?*anyopaque,
2790 dir: Io.Dir,
3084 dir: Dir,
27913085 sub_path: []const u8,
2792 flags: Io.File.OpenFlags,
2793) Io.File.OpenError!Io.File {
3086 flags: File.OpenFlags,
3087) File.OpenError!File {
27943088 if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags);
27953089 const t: *Threaded = @ptrCast(@alignCast(userdata));
27963090 const current_thread = Thread.getCurrent(t);
......@@ -2827,15 +3121,13 @@ fn dirOpenFileWasi(
28273121 while (true) {
28283122 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
28293123 .SUCCESS => {
2830 errdefer posix.close(fd);
28313124 current_thread.endSyscall();
2832 return .{ .handle = fd };
3125 break;
28333126 },
28343127 .INTR => {
28353128 try current_thread.checkCancel();
28363129 continue;
28373130 },
2838 .CANCELED => return current_thread.endSyscallCanceled(),
28393131 else => |e| {
28403132 current_thread.endSyscall();
28413133 switch (e) {
......@@ -2863,6 +3155,21 @@ fn dirOpenFileWasi(
28633155 },
28643156 }
28653157 }
3158 errdefer posix.close(fd);
3159
3160 if (!flags.allow_directory) {
3161 const is_dir = is_dir: {
3162 const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) {
3163 // The directory-ness is either unknown or unknowable
3164 error.Streaming => break :is_dir false,
3165 else => |e| return e,
3166 };
3167 break :is_dir stat.kind == .directory;
3168 };
3169 if (is_dir) return error.IsDir;
3170 }
3171
3172 return .{ .handle = fd };
28663173}
28673174
28683175const dirOpenDir = switch (native_os) {
......@@ -2874,10 +3181,10 @@ const dirOpenDir = switch (native_os) {
28743181/// This function is also used for WASI when libc is linked.
28753182fn dirOpenDirPosix(
28763183 userdata: ?*anyopaque,
2877 dir: Io.Dir,
3184 dir: Dir,
28783185 sub_path: []const u8,
2879 options: Io.Dir.OpenOptions,
2880) Io.Dir.OpenError!Io.Dir {
3186 options: Dir.OpenOptions,
3187) Dir.OpenError!Dir {
28813188 const t: *Threaded = @ptrCast(@alignCast(userdata));
28823189
28833190 if (is_windows) {
......@@ -2919,7 +3226,6 @@ fn dirOpenDirPosix(
29193226 try current_thread.checkCancel();
29203227 continue;
29213228 },
2922 .CANCELED => return current_thread.endSyscallCanceled(),
29233229 else => |e| {
29243230 current_thread.endSyscall();
29253231 switch (e) {
......@@ -2948,10 +3254,10 @@ fn dirOpenDirPosix(
29483254
29493255fn dirOpenDirHaiku(
29503256 userdata: ?*anyopaque,
2951 dir: Io.Dir,
3257 dir: Dir,
29523258 sub_path: []const u8,
2953 options: Io.Dir.OpenOptions,
2954) Io.Dir.OpenError!Io.Dir {
3259 options: Dir.OpenOptions,
3260) Dir.OpenError!Dir {
29553261 const t: *Threaded = @ptrCast(@alignCast(userdata));
29563262 const current_thread = Thread.getCurrent(t);
29573263
......@@ -2972,7 +3278,6 @@ fn dirOpenDirHaiku(
29723278 try current_thread.checkCancel();
29733279 continue;
29743280 },
2975 .CANCELED => return current_thread.endSyscallCanceled(),
29763281 else => |e| {
29773282 current_thread.endSyscall();
29783283 switch (e) {
......@@ -2999,10 +3304,10 @@ fn dirOpenDirHaiku(
29993304
30003305pub fn dirOpenDirWindows(
30013306 t: *Io.Threaded,
3002 dir: Io.Dir,
3307 dir: Dir,
30033308 sub_path_w: [:0]const u16,
3004 options: Io.Dir.OpenOptions,
3005) Io.Dir.OpenError!Io.Dir {
3309 options: Dir.OpenOptions,
3310) Dir.OpenError!Dir {
30063311 const current_thread = Thread.getCurrent(t);
30073312 const w = windows;
30083313
......@@ -3013,7 +3318,7 @@ pub fn dirOpenDirWindows(
30133318 .Buffer = @constCast(sub_path_w.ptr),
30143319 };
30153320 var io_status_block: w.IO_STATUS_BLOCK = undefined;
3016 var result: Io.Dir = .{ .handle = undefined };
3321 var result: Dir = .{ .handle = undefined };
30173322 try current_thread.checkCancel();
30183323 const rc = w.ntdll.NtCreateFile(
30193324 &result.handle,
......@@ -3068,147 +3373,4298 @@ pub fn dirOpenDirWindows(
30683373 }
30693374}
30703375
3071const MakeOpenDirAccessMaskWOptions = struct {
3072 no_follow: bool,
3073 create_disposition: u32,
3074};
3075
3076fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void {
3376fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
30773377 const t: *Threaded = @ptrCast(@alignCast(userdata));
30783378 _ = t;
3079 posix.close(dir.handle);
3080}
3379 for (dirs) |dir| posix.close(dir.handle);
3380}
3381
3382const dirRead = switch (native_os) {
3383 .linux => dirReadLinux,
3384 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => dirReadDarwin,
3385 .freebsd, .netbsd, .dragonfly, .openbsd => dirReadBsd,
3386 .illumos => dirReadIllumos,
3387 .haiku => dirReadHaiku,
3388 .windows => dirReadWindows,
3389 .wasi => dirReadWasi,
3390 else => dirReadUnimplemented,
3391};
30813392
3082fn dirOpenDirWasi(
3083 userdata: ?*anyopaque,
3084 dir: Io.Dir,
3085 sub_path: []const u8,
3086 options: Io.Dir.OpenOptions,
3087) Io.Dir.OpenError!Io.Dir {
3088 if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options);
3393fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3394 const linux = std.os.linux;
30893395 const t: *Threaded = @ptrCast(@alignCast(userdata));
30903396 const current_thread = Thread.getCurrent(t);
3091 const wasi = std.os.wasi;
3092
3093 var base: std.os.wasi.rights_t = .{
3094 .FD_FILESTAT_GET = true,
3095 .FD_FDSTAT_SET_FLAGS = true,
3096 .FD_FILESTAT_SET_TIMES = true,
3097 };
3098 if (options.access_sub_paths) {
3099 base.FD_READDIR = true;
3100 base.PATH_CREATE_DIRECTORY = true;
3101 base.PATH_CREATE_FILE = true;
3102 base.PATH_LINK_SOURCE = true;
3103 base.PATH_LINK_TARGET = true;
3104 base.PATH_OPEN = true;
3105 base.PATH_READLINK = true;
3106 base.PATH_RENAME_SOURCE = true;
3107 base.PATH_RENAME_TARGET = true;
3108 base.PATH_FILESTAT_GET = true;
3109 base.PATH_FILESTAT_SET_SIZE = true;
3110 base.PATH_FILESTAT_SET_TIMES = true;
3111 base.PATH_SYMLINK = true;
3112 base.PATH_REMOVE_DIRECTORY = true;
3113 base.PATH_UNLINK_FILE = true;
3114 }
3115
3116 const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks };
3117 const oflags: wasi.oflags_t = .{ .DIRECTORY = true };
3118 const fdflags: wasi.fdflags_t = .{};
3119 var fd: posix.fd_t = undefined;
3120 try current_thread.beginSyscall();
3121 while (true) {
3122 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) {
3123 .SUCCESS => {
3124 current_thread.endSyscall();
3125 return .{ .handle = fd };
3126 },
3127 .INTR => {
3128 try current_thread.checkCancel();
3129 continue;
3130 },
3131 .CANCELED => return current_thread.endSyscallCanceled(),
3132 else => |e| {
3133 current_thread.endSyscall();
3134 switch (e) {
3135 .FAULT => |err| return errnoBug(err),
3136 .INVAL => return error.BadPathName,
3137 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3138 .ACCES => return error.AccessDenied,
3139 .LOOP => return error.SymLinkLoop,
3140 .MFILE => return error.ProcessFdQuotaExceeded,
3141 .NAMETOOLONG => return error.NameTooLong,
3142 .NFILE => return error.SystemFdQuotaExceeded,
3143 .NODEV => return error.NoDevice,
3144 .NOENT => return error.FileNotFound,
3145 .NOMEM => return error.SystemResources,
3146 .NOTDIR => return error.NotDir,
3147 .PERM => return error.PermissionDenied,
3148 .BUSY => return error.DeviceBusy,
3149 .NOTCAPABLE => return error.AccessDenied,
3150 .ILSEQ => return error.BadPathName,
3151 else => |err| return posix.unexpectedErrno(err),
3397 var buffer_index: usize = 0;
3398 while (buffer.len - buffer_index != 0) {
3399 if (dr.end - dr.index == 0) {
3400 // Refill the buffer, unless we've already created references to
3401 // buffered data.
3402 if (buffer_index != 0) break;
3403 if (dr.state == .reset) {
3404 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
3405 error.Unseekable => return error.Unexpected,
3406 else => |e| return e,
3407 };
3408 dr.state = .reading;
3409 }
3410 try current_thread.beginSyscall();
3411 const n = while (true) {
3412 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
3413 switch (linux.errno(rc)) {
3414 .SUCCESS => {
3415 current_thread.endSyscall();
3416 break rc;
3417 },
3418 .INTR => {
3419 try current_thread.checkCancel();
3420 continue;
3421 },
3422 else => |e| {
3423 current_thread.endSyscall();
3424 switch (e) {
3425 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
3426 .FAULT => |err| return errnoBug(err),
3427 .NOTDIR => |err| return errnoBug(err),
3428 // To be consistent across platforms, iteration
3429 // ends if the directory being iterated is deleted
3430 // during iteration. This matches the behavior of
3431 // non-Linux, non-WASI UNIX platforms.
3432 .NOENT => {
3433 dr.state = .finished;
3434 return 0;
3435 },
3436 // This can occur when reading /proc/$PID/net, or
3437 // if the provided buffer is too small. Neither
3438 // scenario is intended to be handled by this API.
3439 .INVAL => return error.Unexpected,
3440 .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory.
3441 else => |err| return posix.unexpectedErrno(err),
3442 }
3443 },
31523444 }
3153 },
3445 };
3446 if (n == 0) {
3447 dr.state = .finished;
3448 return 0;
3449 }
3450 dr.index = 0;
3451 dr.end = n;
31543452 }
3453 // Linux aligns the header by padding after the null byte of the name
3454 // to align the next entry. This means we can find the end of the name
3455 // by looking at only the 8 bytes before the next record. However since
3456 // file names are usually short it's better to keep the machine code
3457 // simpler.
3458 //
3459 // Furthermore, I observed qemu user mode to not align this struct, so
3460 // this code makes the conservative choice to not assume alignment.
3461 const linux_entry: *align(1) linux.dirent64 = @ptrCast(&dr.buffer[dr.index]);
3462 const next_index = dr.index + linux_entry.reclen;
3463 dr.index = next_index;
3464 const name_ptr: [*]u8 = &linux_entry.name;
3465 const padded_name = name_ptr[0 .. linux_entry.reclen - @offsetOf(linux.dirent64, "name")];
3466 const name_len = std.mem.findScalar(u8, padded_name, 0).?;
3467 const name = name_ptr[0..name_len :0];
3468
3469 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
3470
3471 const entry_kind: File.Kind = switch (linux_entry.type) {
3472 linux.DT.BLK => .block_device,
3473 linux.DT.CHR => .character_device,
3474 linux.DT.DIR => .directory,
3475 linux.DT.FIFO => .named_pipe,
3476 linux.DT.LNK => .sym_link,
3477 linux.DT.REG => .file,
3478 linux.DT.SOCK => .unix_domain_socket,
3479 else => .unknown,
3480 };
3481 buffer[buffer_index] = .{
3482 .name = name,
3483 .kind = entry_kind,
3484 .inode = linux_entry.ino,
3485 };
3486 buffer_index += 1;
31553487 }
3488 return buffer_index;
31563489}
31573490
3158fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
3491fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
31593492 const t: *Threaded = @ptrCast(@alignCast(userdata));
3160 _ = t;
3161 posix.close(file.handle);
3162}
3493 const current_thread = Thread.getCurrent(t);
3494 const Header = extern struct {
3495 seek: i64,
3496 };
3497 const header: *Header = @ptrCast(dr.buffer.ptr);
3498 const header_end: usize = @sizeOf(Header);
3499 if (dr.index < header_end) {
3500 // Initialize header.
3501 dr.index = header_end;
3502 dr.end = header_end;
3503 header.* = .{ .seek = 0 };
3504 }
3505 var buffer_index: usize = 0;
3506 while (buffer.len - buffer_index != 0) {
3507 if (dr.end - dr.index == 0) {
3508 // Refill the buffer, unless we've already created references to
3509 // buffered data.
3510 if (buffer_index != 0) break;
3511 if (dr.state == .reset) {
3512 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
3513 error.Unseekable => return error.Unexpected,
3514 else => |e| return e,
3515 };
3516 dr.state = .reading;
3517 }
3518 const dents_buffer = dr.buffer[header_end..];
3519 try current_thread.beginSyscall();
3520 const n: usize = while (true) {
3521 const rc = posix.system.getdirentries(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, &header.seek);
3522 switch (posix.errno(rc)) {
3523 .SUCCESS => {
3524 current_thread.endSyscall();
3525 break @intCast(rc);
3526 },
3527 .INTR => {
3528 try current_thread.checkCancel();
3529 continue;
3530 },
3531 else => |e| {
3532 current_thread.endSyscall();
3533 switch (e) {
3534 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
3535 .FAULT => |err| return errnoBug(err),
3536 .NOTDIR => |err| return errnoBug(err),
3537 .INVAL => |err| return errnoBug(err),
3538 else => |err| return posix.unexpectedErrno(err),
3539 }
3540 },
3541 }
3542 };
3543 if (n == 0) {
3544 dr.state = .finished;
3545 return 0;
3546 }
3547 dr.index = header_end;
3548 dr.end = header_end + n;
3549 }
3550 const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
3551 const next_index = dr.index + darwin_entry.reclen;
3552 dr.index = next_index;
3553
3554 const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen];
3555 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or (darwin_entry.ino == 0))
3556 continue;
3557
3558 const entry_kind: File.Kind = switch (darwin_entry.type) {
3559 posix.DT.BLK => .block_device,
3560 posix.DT.CHR => .character_device,
3561 posix.DT.DIR => .directory,
3562 posix.DT.FIFO => .named_pipe,
3563 posix.DT.LNK => .sym_link,
3564 posix.DT.REG => .file,
3565 posix.DT.SOCK => .unix_domain_socket,
3566 posix.DT.WHT => .whiteout,
3567 else => .unknown,
3568 };
3569 buffer[buffer_index] = .{
3570 .name = name,
3571 .kind = entry_kind,
3572 .inode = darwin_entry.ino,
3573 };
3574 buffer_index += 1;
3575 }
3576 return buffer_index;
3577}
3578
3579fn dirReadBsd(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3580 const t: *Threaded = @ptrCast(@alignCast(userdata));
3581 const current_thread = Thread.getCurrent(t);
3582 var buffer_index: usize = 0;
3583 while (buffer.len - buffer_index != 0) {
3584 if (dr.end - dr.index == 0) {
3585 // Refill the buffer, unless we've already created references to
3586 // buffered data.
3587 if (buffer_index != 0) break;
3588 if (dr.state == .reset) {
3589 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
3590 error.Unseekable => return error.Unexpected,
3591 else => |e| return e,
3592 };
3593 dr.state = .reading;
3594 }
3595 try current_thread.beginSyscall();
3596 const n: usize = while (true) {
3597 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
3598 switch (posix.errno(rc)) {
3599 .SUCCESS => {
3600 current_thread.endSyscall();
3601 break @intCast(rc);
3602 },
3603 .INTR => {
3604 try current_thread.checkCancel();
3605 continue;
3606 },
3607 else => |e| {
3608 current_thread.endSyscall();
3609 switch (e) {
3610 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
3611 .FAULT => |err| return errnoBug(err),
3612 .NOTDIR => |err| return errnoBug(err),
3613 .INVAL => |err| return errnoBug(err),
3614 // Introduced in freebsd 13.2: directory unlinked
3615 // but still open. To be consistent, iteration ends
3616 // if the directory being iterated is deleted
3617 // during iteration.
3618 .NOENT => {
3619 dr.state = .finished;
3620 return 0;
3621 },
3622 else => |err| return posix.unexpectedErrno(err),
3623 }
3624 },
3625 }
3626 };
3627 if (n == 0) {
3628 dr.state = .finished;
3629 return 0;
3630 }
3631 dr.index = 0;
3632 dr.end = n;
3633 }
3634 const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
3635 const next_index = dr.index +
3636 if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen();
3637 dr.index = next_index;
3638
3639 const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen];
3640
3641 const skip_zero_fileno = switch (native_os) {
3642 // fileno=0 is used to mark invalid entries or deleted files.
3643 .openbsd, .netbsd => true,
3644 else => false,
3645 };
3646 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..") or
3647 (skip_zero_fileno and bsd_entry.fileno == 0))
3648 {
3649 continue;
3650 }
3651
3652 const entry_kind: File.Kind = switch (bsd_entry.type) {
3653 posix.DT.BLK => .block_device,
3654 posix.DT.CHR => .character_device,
3655 posix.DT.DIR => .directory,
3656 posix.DT.FIFO => .named_pipe,
3657 posix.DT.LNK => .sym_link,
3658 posix.DT.REG => .file,
3659 posix.DT.SOCK => .unix_domain_socket,
3660 posix.DT.WHT => .whiteout,
3661 else => .unknown,
3662 };
3663 buffer[buffer_index] = .{
3664 .name = name,
3665 .kind = entry_kind,
3666 .inode = bsd_entry.fileno,
3667 };
3668 buffer_index += 1;
3669 }
3670 return buffer_index;
3671}
3672
3673fn dirReadIllumos(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3674 const t: *Threaded = @ptrCast(@alignCast(userdata));
3675 const current_thread = Thread.getCurrent(t);
3676 var buffer_index: usize = 0;
3677 while (buffer.len - buffer_index != 0) {
3678 if (dr.end - dr.index == 0) {
3679 // Refill the buffer, unless we've already created references to
3680 // buffered data.
3681 if (buffer_index != 0) break;
3682 if (dr.state == .reset) {
3683 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
3684 error.Unseekable => return error.Unexpected,
3685 else => |e| return e,
3686 };
3687 dr.state = .reading;
3688 }
3689 try current_thread.beginSyscall();
3690 const n: usize = while (true) {
3691 const rc = posix.system.getdents(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
3692 switch (posix.errno(rc)) {
3693 .SUCCESS => {
3694 current_thread.endSyscall();
3695 break rc;
3696 },
3697 .INTR => {
3698 try current_thread.checkCancel();
3699 continue;
3700 },
3701 else => |e| {
3702 current_thread.endSyscall();
3703 switch (e) {
3704 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability
3705 .FAULT => |err| return errnoBug(err),
3706 .NOTDIR => |err| return errnoBug(err),
3707 .INVAL => |err| return errnoBug(err),
3708 else => |err| return posix.unexpectedErrno(err),
3709 }
3710 },
3711 }
3712 };
3713 if (n == 0) {
3714 dr.state = .finished;
3715 return 0;
3716 }
3717 dr.index = 0;
3718 dr.end = n;
3719 }
3720 const entry = @as(*align(1) posix.system.dirent, @ptrCast(&dr.buffer[dr.index]));
3721 const next_index = dr.index + entry.reclen;
3722 dr.index = next_index;
3723
3724 const name = std.mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
3725 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) continue;
3726
3727 // illumos dirent doesn't expose type, so we have to call stat to get it.
3728 const stat = try posixStatFile(current_thread, dr.dir.handle, name, posix.AT.SYMLINK_NOFOLLOW);
3729
3730 buffer[buffer_index] = .{
3731 .name = name,
3732 .kind = stat.kind,
3733 .inode = entry.ino,
3734 };
3735 buffer_index += 1;
3736 }
3737 return buffer_index;
3738}
3739
3740fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3741 _ = userdata;
3742 _ = dr;
3743 _ = buffer;
3744 @panic("TODO implement dirReadHaiku");
3745}
3746
3747fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3748 const t: *Threaded = @ptrCast(@alignCast(userdata));
3749 const current_thread = Thread.getCurrent(t);
3750 const w = windows;
3751
3752 // We want to be able to use the `dr.buffer` for both the NtQueryDirectoryFile call (which
3753 // returns WTF-16 names) *and* as a buffer for storing those WTF-16 names as WTF-8 to be able
3754 // to return them in `Dir.Entry.name`. However, the problem that needs to be overcome in order to do
3755 // that is that each WTF-16 code unit can be encoded as a maximum of 3 WTF-8 bytes, which means
3756 // that it's not guaranteed that the memory used for the WTF-16 name will be sufficient
3757 // for the WTF-8 encoding of the same name (for example, € is encoded as one WTF-16 code unit,
3758 // [2 bytes] but encoded in WTF-8 as 3 bytes).
3759 //
3760 // The approach taken here is to "reserve" enough space in the `dr.buffer` to ensure that
3761 // at least one entry with the maximum possible WTF-8 name length can be stored without clobbering
3762 // any entries that follow it. That is, we determine how much space is needed to allow that,
3763 // and then only provide the remaining portion of `dr.buffer` to the NtQueryDirectoryFile
3764 // call. The WTF-16 names can then be safely converted using the full `dr.buffer` slice, making
3765 // sure that each name can only potentially overwrite the data of its own entry.
3766 //
3767 // The worst case, where an entry's name is both the maximum length of a component and
3768 // made up entirely of code points that are encoded as one WTF-16 code unit/three WTF-8 bytes,
3769 // would therefore look like the diagram below, and only one entry would be able to be returned:
3770 //
3771 // | reserved | remaining unreserved buffer |
3772 // | entry 1 | entry 2 | ... |
3773 // | wtf-8 name of entry 1 |
3774 //
3775 // However, in the average case we will be able to store more than one WTF-8 name at a time in the
3776 // available buffer and therefore we will be able to populate more than one `Dir.Entry` at a time.
3777 // That might look something like this (where name 1, name 2, etc are the converted WTF-8 names):
3778 //
3779 // | reserved | remaining unreserved buffer |
3780 // | entry 1 | entry 2 | ... |
3781 // | name 1 | name 2 | name 3 | name 4 | ... |
3782 //
3783 // Note: More than the minimum amount of space could be reserved to make the "worst case"
3784 // less likely, but since the worst-case also requires a maximum length component to matter,
3785 // it's unlikely for it to become a problem in normal scenarios even if all names on the filesystem
3786 // are made up of non-ASCII characters that have the "one WTF-16 code unit <-> three WTF-8 bytes"
3787 // property (e.g. code points >= U+0800 and <= U+FFFF), as it's unlikely for a significant
3788 // number of components to be maximum length.
3789
3790 // We need `3 * NAME_MAX` bytes to store a max-length component as WTF-8 safely.
3791 // Because needing to store a max-length component depends on a `FileName` *with* the maximum
3792 // component length, we know that the corresponding populated `FILE_BOTH_DIR_INFORMATION` will
3793 // be of size `@sizeOf(w.FILE_BOTH_DIR_INFORMATION) + 2 * NAME_MAX` bytes, so we only need to
3794 // reserve enough to get us to up to having `3 * NAME_MAX` bytes available when taking into account
3795 // that we have the ability to write over top of the reserved memory + the full footprint of that
3796 // particular `FILE_BOTH_DIR_INFORMATION`.
3797 const max_info_len = @sizeOf(w.FILE_BOTH_DIR_INFORMATION) + w.NAME_MAX * 2;
3798 const info_align = @alignOf(w.FILE_BOTH_DIR_INFORMATION);
3799 const reserve_needed = std.mem.alignForward(usize, Dir.max_name_bytes, info_align) - max_info_len;
3800 const unreserved_start = std.mem.alignForward(usize, reserve_needed, info_align);
3801 const unreserved_buffer = dr.buffer[unreserved_start..];
3802 // This is enforced by `Dir.Reader`
3803 assert(unreserved_buffer.len >= max_info_len);
3804
3805 var name_index: usize = 0;
3806 var buffer_index: usize = 0;
3807 while (buffer.len - buffer_index != 0) {
3808 if (dr.end - dr.index == 0) {
3809 // Refill the buffer, unless we've already created references to
3810 // buffered data.
3811 if (buffer_index != 0) break;
3812
3813 try current_thread.checkCancel();
3814 var io_status_block: w.IO_STATUS_BLOCK = undefined;
3815 const rc = w.ntdll.NtQueryDirectoryFile(
3816 dr.dir.handle,
3817 null,
3818 null,
3819 null,
3820 &io_status_block,
3821 unreserved_buffer.ptr,
3822 std.math.lossyCast(w.ULONG, unreserved_buffer.len),
3823 .BothDirectory,
3824 w.FALSE,
3825 null,
3826 @intFromBool(dr.state == .reset),
3827 );
3828 dr.state = .reading;
3829 if (io_status_block.Information == 0) {
3830 dr.state = .finished;
3831 return 0;
3832 }
3833 dr.index = 0;
3834 dr.end = io_status_block.Information;
3835 switch (rc) {
3836 .SUCCESS => {},
3837 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
3838 else => return w.unexpectedStatus(rc),
3839 }
3840 }
3841
3842 // While the official API docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
3843 // this may not always be the case (e.g. due to faulty VM/sandboxing tools)
3844 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&unreserved_buffer[dr.index]));
3845 const backtrack_index = dr.index;
3846 if (dir_info.NextEntryOffset != 0) {
3847 dr.index += dir_info.NextEntryOffset;
3848 } else {
3849 dr.index = dr.end;
3850 }
3851
3852 const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
3853
3854 if (std.mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or std.mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' })) {
3855 continue;
3856 }
3857
3858 // Read any relevant information from the `dir_info` now since it's possible the WTF-8
3859 // name will overwrite it.
3860 const kind: File.Kind = blk: {
3861 const attrs = dir_info.FileAttributes;
3862 if (attrs.REPARSE_POINT) break :blk .sym_link;
3863 if (attrs.DIRECTORY) break :blk .directory;
3864 break :blk .file;
3865 };
3866 const inode: File.INode = dir_info.FileIndex;
3867
3868 // If there's no more space for WTF-8 names without bleeding over into
3869 // the remaining unprocessed entries, then backtrack and return what we have so far.
3870 if (name_index + std.unicode.calcWtf8Len(name_wtf16le) > unreserved_start + dr.index) {
3871 // We should always be able to fit at least one entry into the buffer no matter what
3872 assert(buffer_index != 0);
3873 dr.index = backtrack_index;
3874 break;
3875 }
3876
3877 const name_buf = dr.buffer[name_index..];
3878 const name_wtf8_len = std.unicode.wtf16LeToWtf8(name_buf, name_wtf16le);
3879 const name_wtf8 = name_buf[0..name_wtf8_len];
3880 name_index += name_wtf8_len;
3881
3882 buffer[buffer_index] = .{
3883 .name = name_wtf8,
3884 .kind = kind,
3885 .inode = inode,
3886 };
3887 buffer_index += 1;
3888 }
3889
3890 return buffer_index;
3891}
3892
3893fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3894 // We intentinally use fd_readdir even when linked with libc, since its
3895 // implementation is exactly the same as below, and we avoid the code
3896 // complexity here.
3897 const wasi = std.os.wasi;
3898 const t: *Threaded = @ptrCast(@alignCast(userdata));
3899 const current_thread = Thread.getCurrent(t);
3900 const Header = extern struct {
3901 cookie: u64,
3902 };
3903 const header: *align(@alignOf(usize)) Header = @ptrCast(dr.buffer.ptr);
3904 const header_end: usize = @sizeOf(Header);
3905 if (dr.index < header_end) {
3906 // Initialize header.
3907 dr.index = header_end;
3908 dr.end = header_end;
3909 header.* = .{ .cookie = wasi.DIRCOOKIE_START };
3910 }
3911 var buffer_index: usize = 0;
3912 while (buffer.len - buffer_index != 0) {
3913 // According to the WASI spec, the last entry might be truncated, so we
3914 // need to check if the remaining buffer contains the whole dirent.
3915 if (dr.end - dr.index < @sizeOf(wasi.dirent_t)) {
3916 // Refill the buffer, unless we've already created references to
3917 // buffered data.
3918 if (buffer_index != 0) break;
3919 if (dr.state == .reset) {
3920 header.* = .{ .cookie = wasi.DIRCOOKIE_START };
3921 dr.state = .reading;
3922 }
3923 const dents_buffer = dr.buffer[header_end..];
3924 var n: usize = undefined;
3925 try current_thread.beginSyscall();
3926 while (true) {
3927 switch (wasi.fd_readdir(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, header.cookie, &n)) {
3928 .SUCCESS => {
3929 current_thread.endSyscall();
3930 break;
3931 },
3932 .INTR => {
3933 try current_thread.checkCancel();
3934 continue;
3935 },
3936 else => |e| {
3937 current_thread.endSyscall();
3938 switch (e) {
3939 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
3940 .FAULT => |err| return errnoBug(err),
3941 .NOTDIR => |err| return errnoBug(err),
3942 .INVAL => |err| return errnoBug(err),
3943 // To be consistent across platforms, iteration
3944 // ends if the directory being iterated is deleted
3945 // during iteration. This matches the behavior of
3946 // non-Linux, non-WASI UNIX platforms.
3947 .NOENT => {
3948 dr.state = .finished;
3949 return 0;
3950 },
3951 .NOTCAPABLE => return error.AccessDenied,
3952 else => |err| return posix.unexpectedErrno(err),
3953 }
3954 },
3955 }
3956 }
3957 if (n == 0) {
3958 dr.state = .finished;
3959 return 0;
3960 }
3961 dr.index = header_end;
3962 dr.end = header_end + n;
3963 }
3964 const entry: *align(1) wasi.dirent_t = @ptrCast(&dr.buffer[dr.index]);
3965 const entry_size = @sizeOf(wasi.dirent_t);
3966 const name_index = dr.index + entry_size;
3967 if (name_index + entry.namlen > dr.end) {
3968 // This case, the name is truncated, so we need to call readdir to store the entire name.
3969 dr.end = dr.index; // Force fd_readdir in the next loop.
3970 continue;
3971 }
3972 const name = dr.buffer[name_index..][0..entry.namlen];
3973 const next_index = name_index + entry.namlen;
3974 dr.index = next_index;
3975 header.cookie = entry.next;
3976
3977 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, ".."))
3978 continue;
3979
3980 const entry_kind: File.Kind = switch (entry.type) {
3981 .BLOCK_DEVICE => .block_device,
3982 .CHARACTER_DEVICE => .character_device,
3983 .DIRECTORY => .directory,
3984 .SYMBOLIC_LINK => .sym_link,
3985 .REGULAR_FILE => .file,
3986 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
3987 else => .unknown,
3988 };
3989 buffer[buffer_index] = .{
3990 .name = name,
3991 .kind = entry_kind,
3992 .inode = entry.ino,
3993 };
3994 buffer_index += 1;
3995 }
3996 return buffer_index;
3997}
3998
3999fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
4000 _ = userdata;
4001 _ = dir_reader;
4002 _ = buffer;
4003 return error.Unimplemented;
4004}
4005
4006const dirRealPathFile = switch (native_os) {
4007 .windows => dirRealPathFileWindows,
4008 else => dirRealPathFilePosix,
4009};
4010
4011fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize {
4012 const t: *Threaded = @ptrCast(@alignCast(userdata));
4013 const current_thread = Thread.getCurrent(t);
4014
4015 try current_thread.checkCancel();
4016
4017 var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
4018
4019 const h_file = blk: {
4020 const res = windows.OpenFile(path_name_w.span(), .{
4021 .dir = dir.handle,
4022 .access_mask = .{
4023 .GENERIC = .{ .READ = true },
4024 .STANDARD = .{ .SYNCHRONIZE = true },
4025 },
4026 .creation = .OPEN,
4027 .filter = .any,
4028 }) catch |err| switch (err) {
4029 error.WouldBlock => unreachable,
4030 else => |e| return e,
4031 };
4032 break :blk res;
4033 };
4034 defer windows.CloseHandle(h_file);
4035 return realPathWindows(current_thread, h_file, out_buffer);
4036}
4037
4038fn realPathWindows(current_thread: *Thread, h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
4039 _ = current_thread; // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
4040 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
4041 const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
4042
4043 const len = std.unicode.calcWtf8Len(wide_slice);
4044 if (len > out_buffer.len)
4045 return error.NameTooLong;
4046
4047 return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
4048}
4049
4050fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize {
4051 if (native_os == .wasi) return error.OperationUnsupported;
4052
4053 const t: *Threaded = @ptrCast(@alignCast(userdata));
4054 const current_thread = Thread.getCurrent(t);
4055
4056 var path_buffer: [posix.PATH_MAX]u8 = undefined;
4057 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
4058
4059 if (builtin.link_libc and dir.handle == posix.AT.FDCWD) {
4060 if (out_buffer.len < posix.PATH_MAX) return error.NameTooLong;
4061 try current_thread.beginSyscall();
4062 while (true) {
4063 if (std.c.realpath(sub_path_posix, out_buffer.ptr)) |redundant_pointer| {
4064 current_thread.endSyscall();
4065 assert(redundant_pointer == out_buffer.ptr);
4066 return std.mem.indexOfScalar(u8, out_buffer, 0) orelse out_buffer.len;
4067 }
4068 const err: posix.E = @enumFromInt(std.c._errno().*);
4069 if (err == .INTR) {
4070 try current_thread.checkCancel();
4071 continue;
4072 }
4073 current_thread.endSyscall();
4074 switch (err) {
4075 .INVAL => return errnoBug(err),
4076 .BADF => return errnoBug(err),
4077 .FAULT => return errnoBug(err),
4078 .ACCES => return error.AccessDenied,
4079 .NOENT => return error.FileNotFound,
4080 .OPNOTSUPP => return error.OperationUnsupported,
4081 .NOTDIR => return error.NotDir,
4082 .NAMETOOLONG => return error.NameTooLong,
4083 .LOOP => return error.SymLinkLoop,
4084 .IO => return error.InputOutput,
4085 else => return posix.unexpectedErrno(err),
4086 }
4087 }
4088 }
4089
4090 var flags: posix.O = .{};
4091 if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true;
4092 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
4093 if (@hasField(posix.O, "PATH")) flags.PATH = true;
4094
4095 const mode: posix.mode_t = 0;
4096
4097 try current_thread.beginSyscall();
4098 const fd: posix.fd_t = while (true) {
4099 const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
4100 switch (posix.errno(rc)) {
4101 .SUCCESS => {
4102 current_thread.endSyscall();
4103 break @intCast(rc);
4104 },
4105 .INTR => {
4106 try current_thread.checkCancel();
4107 continue;
4108 },
4109 else => |e| {
4110 current_thread.endSyscall();
4111 switch (e) {
4112 .FAULT => |err| return errnoBug(err),
4113 .INVAL => return error.BadPathName,
4114 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4115 .ACCES => return error.AccessDenied,
4116 .FBIG => return error.FileTooBig,
4117 .OVERFLOW => return error.FileTooBig,
4118 .ISDIR => return error.IsDir,
4119 .LOOP => return error.SymLinkLoop,
4120 .MFILE => return error.ProcessFdQuotaExceeded,
4121 .NAMETOOLONG => return error.NameTooLong,
4122 .NFILE => return error.SystemFdQuotaExceeded,
4123 .NODEV => return error.NoDevice,
4124 .NOENT => return error.FileNotFound,
4125 .SRCH => return error.FileNotFound, // Linux when accessing procfs.
4126 .NOMEM => return error.SystemResources,
4127 .NOSPC => return error.NoSpaceLeft,
4128 .NOTDIR => return error.NotDir,
4129 .PERM => return error.PermissionDenied,
4130 .EXIST => return error.PathAlreadyExists,
4131 .BUSY => return error.DeviceBusy,
4132 .NXIO => return error.NoDevice,
4133 .ILSEQ => return error.BadPathName,
4134 else => |err| return posix.unexpectedErrno(err),
4135 }
4136 },
4137 }
4138 };
4139 defer posix.close(fd);
4140 return realPathPosix(current_thread, fd, out_buffer);
4141}
4142
4143const dirRealPath = switch (native_os) {
4144 .windows => dirRealPathWindows,
4145 else => dirRealPathPosix,
4146};
4147
4148fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
4149 if (native_os == .wasi) return error.OperationUnsupported;
4150 const t: *Threaded = @ptrCast(@alignCast(userdata));
4151 const current_thread = Thread.getCurrent(t);
4152 return realPathPosix(current_thread, dir.handle, out_buffer);
4153}
4154
4155fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
4156 const t: *Threaded = @ptrCast(@alignCast(userdata));
4157 const current_thread = Thread.getCurrent(t);
4158 return realPathWindows(current_thread, dir.handle, out_buffer);
4159}
4160
4161const fileRealPath = switch (native_os) {
4162 .windows => fileRealPathWindows,
4163 else => fileRealPathPosix,
4164};
4165
4166fn fileRealPathWindows(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
4167 if (native_os == .wasi) return error.OperationUnsupported;
4168 const t: *Threaded = @ptrCast(@alignCast(userdata));
4169 const current_thread = Thread.getCurrent(t);
4170 return realPathWindows(current_thread, file.handle, out_buffer);
4171}
4172
4173fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
4174 if (native_os == .wasi) return error.OperationUnsupported;
4175 const t: *Threaded = @ptrCast(@alignCast(userdata));
4176 const current_thread = Thread.getCurrent(t);
4177 return realPathPosix(current_thread, file.handle, out_buffer);
4178}
4179
4180fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
4181 switch (native_os) {
4182 .netbsd, .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
4183 var sufficient_buffer: [posix.PATH_MAX]u8 = undefined;
4184 @memset(&sufficient_buffer, 0);
4185 try current_thread.beginSyscall();
4186 while (true) {
4187 switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, &sufficient_buffer))) {
4188 .SUCCESS => {
4189 current_thread.endSyscall();
4190 break;
4191 },
4192 .INTR => {
4193 try current_thread.checkCancel();
4194 continue;
4195 },
4196 else => |e| {
4197 current_thread.endSyscall();
4198 switch (e) {
4199 .ACCES => return error.AccessDenied,
4200 .BADF => return error.FileNotFound,
4201 .NOENT => return error.FileNotFound,
4202 .NOMEM => return error.SystemResources,
4203 .NOSPC => return error.NameTooLong,
4204 .RANGE => return error.NameTooLong,
4205 else => |err| return posix.unexpectedErrno(err),
4206 }
4207 },
4208 }
4209 }
4210 const n = std.mem.indexOfScalar(u8, &sufficient_buffer, 0) orelse sufficient_buffer.len;
4211 if (n > out_buffer.len) return error.NameTooLong;
4212 @memcpy(out_buffer[0..n], sufficient_buffer[0..n]);
4213 return n;
4214 },
4215 .linux, .serenity, .illumos => {
4216 var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
4217 const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}";
4218 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable;
4219 try current_thread.beginSyscall();
4220 while (true) {
4221 const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len);
4222 switch (posix.errno(rc)) {
4223 .SUCCESS => {
4224 current_thread.endSyscall();
4225 const len: usize = @bitCast(rc);
4226 return len;
4227 },
4228 .INTR => {
4229 try current_thread.checkCancel();
4230 continue;
4231 },
4232 else => |e| {
4233 current_thread.endSyscall();
4234 switch (e) {
4235 .ACCES => return error.AccessDenied,
4236 .FAULT => |err| return errnoBug(err),
4237 .IO => return error.FileSystem,
4238 .LOOP => return error.SymLinkLoop,
4239 .NAMETOOLONG => return error.NameTooLong,
4240 .NOENT => return error.FileNotFound,
4241 .NOMEM => return error.SystemResources,
4242 .NOTDIR => return error.NotDir,
4243 .ILSEQ => |err| return errnoBug(err),
4244 else => |err| return posix.unexpectedErrno(err),
4245 }
4246 },
4247 }
4248 }
4249 },
4250 .freebsd => {
4251 var k_file: std.c.kinfo_file = undefined;
4252 k_file.structsize = std.c.KINFO_FILE_SIZE;
4253 try current_thread.beginSyscall();
4254 while (true) {
4255 switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&k_file)))) {
4256 .SUCCESS => {
4257 current_thread.endSyscall();
4258 break;
4259 },
4260 .INTR => {
4261 try current_thread.checkCancel();
4262 continue;
4263 },
4264 .BADF => {
4265 current_thread.endSyscall();
4266 return error.FileNotFound;
4267 },
4268 else => |err| {
4269 current_thread.endSyscall();
4270 return posix.unexpectedErrno(err);
4271 },
4272 }
4273 }
4274 const len = std.mem.findScalar(u8, &k_file.path, 0) orelse k_file.path.len;
4275 if (len == 0) return error.NameTooLong;
4276 @memcpy(out_buffer[0..len], k_file.path[0..len]);
4277 return len;
4278 },
4279 else => return error.OperationUnsupported,
4280 }
4281 comptime unreachable;
4282}
4283
4284const dirDeleteFile = switch (native_os) {
4285 .windows => dirDeleteFileWindows,
4286 .wasi => dirDeleteFileWasi,
4287 else => dirDeleteFilePosix,
4288};
4289
4290fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
4291 return dirDeleteWindows(userdata, dir, sub_path, false) catch |err| switch (err) {
4292 error.DirNotEmpty => unreachable,
4293 else => |e| return e,
4294 };
4295}
4296
4297fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
4298 if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path);
4299 const t: *Threaded = @ptrCast(@alignCast(userdata));
4300 const current_thread = Thread.getCurrent(t);
4301 try current_thread.beginSyscall();
4302 while (true) {
4303 const res = std.os.wasi.path_unlink_file(dir.handle, sub_path.ptr, sub_path.len);
4304 switch (res) {
4305 .SUCCESS => {
4306 current_thread.endSyscall();
4307 return;
4308 },
4309 .INTR => {
4310 try current_thread.checkCancel();
4311 continue;
4312 },
4313 else => |e| {
4314 current_thread.endSyscall();
4315 switch (e) {
4316 .ACCES => return error.AccessDenied,
4317 .PERM => return error.PermissionDenied,
4318 .BUSY => return error.FileBusy,
4319 .FAULT => |err| return errnoBug(err),
4320 .IO => return error.FileSystem,
4321 .ISDIR => return error.IsDir,
4322 .LOOP => return error.SymLinkLoop,
4323 .NAMETOOLONG => return error.NameTooLong,
4324 .NOENT => return error.FileNotFound,
4325 .NOTDIR => return error.NotDir,
4326 .NOMEM => return error.SystemResources,
4327 .ROFS => return error.ReadOnlyFileSystem,
4328 .NOTCAPABLE => return error.AccessDenied,
4329 .ILSEQ => return error.BadPathName,
4330 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
4331 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4332 else => |err| return posix.unexpectedErrno(err),
4333 }
4334 },
4335 }
4336 }
4337}
4338
4339fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
4340 const t: *Threaded = @ptrCast(@alignCast(userdata));
4341 const current_thread = Thread.getCurrent(t);
4342
4343 var path_buffer: [posix.PATH_MAX]u8 = undefined;
4344 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
4345
4346 try current_thread.beginSyscall();
4347 while (true) {
4348 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, 0))) {
4349 .SUCCESS => {
4350 current_thread.endSyscall();
4351 return;
4352 },
4353 .INTR => {
4354 try current_thread.checkCancel();
4355 continue;
4356 },
4357 // Some systems return permission errors when trying to delete a
4358 // directory, so we need to handle that case specifically and
4359 // translate the error.
4360 .PERM => switch (native_os) {
4361 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
4362
4363 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them).
4364 var st = std.mem.zeroes(posix.Stat);
4365 while (true) {
4366 try current_thread.checkCancel();
4367 switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &st, posix.AT.SYMLINK_NOFOLLOW))) {
4368 .SUCCESS => {
4369 current_thread.endSyscall();
4370 break;
4371 },
4372 .INTR => continue,
4373 else => {
4374 current_thread.endSyscall();
4375 return error.PermissionDenied;
4376 },
4377 }
4378 }
4379 const is_dir = st.mode & posix.S.IFMT == posix.S.IFDIR;
4380 if (is_dir)
4381 return error.IsDir
4382 else
4383 return error.PermissionDenied;
4384 },
4385 else => {
4386 current_thread.endSyscall();
4387 return error.PermissionDenied;
4388 },
4389 },
4390 else => |e| {
4391 current_thread.endSyscall();
4392 switch (e) {
4393 .ACCES => return error.AccessDenied,
4394 .BUSY => return error.FileBusy,
4395 .FAULT => |err| return errnoBug(err),
4396 .IO => return error.FileSystem,
4397 .ISDIR => return error.IsDir,
4398 .LOOP => return error.SymLinkLoop,
4399 .NAMETOOLONG => return error.NameTooLong,
4400 .NOENT => return error.FileNotFound,
4401 .NOTDIR => return error.NotDir,
4402 .NOMEM => return error.SystemResources,
4403 .ROFS => return error.ReadOnlyFileSystem,
4404 .EXIST => |err| return errnoBug(err),
4405 .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR
4406 .ILSEQ => return error.BadPathName,
4407 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
4408 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4409 else => |err| return posix.unexpectedErrno(err),
4410 }
4411 },
4412 }
4413 }
4414}
4415
4416const dirDeleteDir = switch (native_os) {
4417 .windows => dirDeleteDirWindows,
4418 .wasi => dirDeleteDirWasi,
4419 else => dirDeleteDirPosix,
4420};
4421
4422fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void {
4423 return dirDeleteWindows(userdata, dir, sub_path, true) catch |err| switch (err) {
4424 error.IsDir => unreachable,
4425 else => |e| return e,
4426 };
4427}
4428
4429fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remove_dir: bool) (Dir.DeleteDirError || Dir.DeleteFileError)!void {
4430 const t: *Threaded = @ptrCast(@alignCast(userdata));
4431 const current_thread = Thread.getCurrent(t);
4432 const w = windows;
4433
4434 try current_thread.checkCancel();
4435
4436 const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path);
4437 const sub_path_w = sub_path_w_buf.span();
4438
4439 const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2));
4440 var nt_name: w.UNICODE_STRING = .{
4441 .Length = path_len_bytes,
4442 .MaximumLength = path_len_bytes,
4443 // The Windows API makes this mutable, but it will not mutate here.
4444 .Buffer = @constCast(sub_path_w.ptr),
4445 };
4446
4447 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
4448 // Windows does not recognize this, but it does work with empty string.
4449 nt_name.Length = 0;
4450 }
4451 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
4452 // Can't remove the parent directory with an open handle.
4453 return error.FileBusy;
4454 }
4455
4456 var io_status_block: w.IO_STATUS_BLOCK = undefined;
4457 var tmp_handle: w.HANDLE = undefined;
4458 var rc = w.ntdll.NtCreateFile(
4459 &tmp_handle,
4460 .{ .STANDARD = .{
4461 .RIGHTS = .{ .DELETE = true },
4462 .SYNCHRONIZE = true,
4463 } },
4464 &.{
4465 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
4466 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
4467 .Attributes = .{},
4468 .ObjectName = &nt_name,
4469 .SecurityDescriptor = null,
4470 .SecurityQualityOfService = null,
4471 },
4472 &io_status_block,
4473 null,
4474 .{},
4475 .VALID_FLAGS,
4476 .OPEN,
4477 .{
4478 .DIRECTORY_FILE = remove_dir,
4479 .NON_DIRECTORY_FILE = !remove_dir,
4480 .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
4481 },
4482 null,
4483 0,
4484 );
4485 switch (rc) {
4486 .SUCCESS => {},
4487 .OBJECT_NAME_INVALID => |err| return w.statusBug(err),
4488 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
4489 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
4490 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
4491 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
4492 .INVALID_PARAMETER => |err| return w.statusBug(err),
4493 .FILE_IS_A_DIRECTORY => return error.IsDir,
4494 .NOT_A_DIRECTORY => return error.NotDir,
4495 .SHARING_VIOLATION => return error.FileBusy,
4496 .ACCESS_DENIED => return error.AccessDenied,
4497 .DELETE_PENDING => return,
4498 else => return w.unexpectedStatus(rc),
4499 }
4500 defer w.CloseHandle(tmp_handle);
4501
4502 // FileDispositionInformationEx has varying levels of support:
4503 // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1
4504 // (INVALID_INFO_CLASS is returned if not supported)
4505 // - Requires the NTFS filesystem
4506 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
4507 // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1
4508 // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
4509 // (NOT_SUPPORTED is returned if a flag is unsupported)
4510 //
4511 // The strategy here is just to try using FileDispositionInformationEx and fall back to
4512 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
4513 const need_fallback = need_fallback: {
4514 try current_thread.checkCancel();
4515
4516 // Deletion with posix semantics if the filesystem supports it.
4517 const info: w.FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{
4518 .DELETE = true,
4519 .POSIX_SEMANTICS = true,
4520 .IGNORE_READONLY_ATTRIBUTE = true,
4521 } };
4522
4523 rc = w.ntdll.NtSetInformationFile(
4524 tmp_handle,
4525 &io_status_block,
4526 &info,
4527 @sizeOf(w.FILE.DISPOSITION.INFORMATION.EX),
4528 .DispositionEx,
4529 );
4530 switch (rc) {
4531 .SUCCESS => return,
4532 // The filesystem does not support FileDispositionInformationEx
4533 .INVALID_PARAMETER,
4534 // The operating system does not support FileDispositionInformationEx
4535 .INVALID_INFO_CLASS,
4536 // The operating system does not support one of the flags
4537 .NOT_SUPPORTED,
4538 => break :need_fallback true,
4539 // For all other statuses, fall down to the switch below to handle them.
4540 else => break :need_fallback false,
4541 }
4542 };
4543
4544 if (need_fallback) {
4545 try current_thread.checkCancel();
4546
4547 // Deletion with file pending semantics, which requires waiting or moving
4548 // files to get them removed (from here).
4549 const file_dispo: w.FILE.DISPOSITION.INFORMATION = .{
4550 .DeleteFile = w.TRUE,
4551 };
4552
4553 rc = w.ntdll.NtSetInformationFile(
4554 tmp_handle,
4555 &io_status_block,
4556 &file_dispo,
4557 @sizeOf(w.FILE.DISPOSITION.INFORMATION),
4558 .Disposition,
4559 );
4560 }
4561 switch (rc) {
4562 .SUCCESS => {},
4563 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
4564 .INVALID_PARAMETER => |err| return w.statusBug(err),
4565 .CANNOT_DELETE => return error.AccessDenied,
4566 .MEDIA_WRITE_PROTECTED => return error.AccessDenied,
4567 .ACCESS_DENIED => return error.AccessDenied,
4568 else => return w.unexpectedStatus(rc),
4569 }
4570}
4571
4572fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void {
4573 if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path);
4574
4575 const t: *Threaded = @ptrCast(@alignCast(userdata));
4576 const current_thread = Thread.getCurrent(t);
4577
4578 try current_thread.beginSyscall();
4579 while (true) {
4580 const res = std.os.wasi.path_remove_directory(dir.handle, sub_path.ptr, sub_path.len);
4581 switch (res) {
4582 .SUCCESS => {
4583 current_thread.endSyscall();
4584 return;
4585 },
4586 .INTR => {
4587 try current_thread.checkCancel();
4588 continue;
4589 },
4590 else => |e| {
4591 current_thread.endSyscall();
4592 switch (e) {
4593 .ACCES => return error.AccessDenied,
4594 .PERM => return error.PermissionDenied,
4595 .BUSY => return error.FileBusy,
4596 .FAULT => |err| return errnoBug(err),
4597 .IO => return error.FileSystem,
4598 .LOOP => return error.SymLinkLoop,
4599 .NAMETOOLONG => return error.NameTooLong,
4600 .NOENT => return error.FileNotFound,
4601 .NOTDIR => return error.NotDir,
4602 .NOMEM => return error.SystemResources,
4603 .ROFS => return error.ReadOnlyFileSystem,
4604 .NOTEMPTY => return error.DirNotEmpty,
4605 .NOTCAPABLE => return error.AccessDenied,
4606 .ILSEQ => return error.BadPathName,
4607 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
4608 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4609 else => |err| return posix.unexpectedErrno(err),
4610 }
4611 },
4612 }
4613 }
4614}
4615
4616fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteDirError!void {
4617 const t: *Threaded = @ptrCast(@alignCast(userdata));
4618 const current_thread = Thread.getCurrent(t);
4619
4620 var path_buffer: [posix.PATH_MAX]u8 = undefined;
4621 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
4622
4623 try current_thread.beginSyscall();
4624 while (true) {
4625 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, posix.AT.REMOVEDIR))) {
4626 .SUCCESS => {
4627 current_thread.endSyscall();
4628 return;
4629 },
4630 .INTR => {
4631 try current_thread.checkCancel();
4632 continue;
4633 },
4634 else => |e| {
4635 current_thread.endSyscall();
4636 switch (e) {
4637 .ACCES => return error.AccessDenied,
4638 .PERM => return error.PermissionDenied,
4639 .BUSY => return error.FileBusy,
4640 .FAULT => |err| return errnoBug(err),
4641 .IO => return error.FileSystem,
4642 .ISDIR => |err| return errnoBug(err),
4643 .LOOP => return error.SymLinkLoop,
4644 .NAMETOOLONG => return error.NameTooLong,
4645 .NOENT => return error.FileNotFound,
4646 .NOTDIR => return error.NotDir,
4647 .NOMEM => return error.SystemResources,
4648 .ROFS => return error.ReadOnlyFileSystem,
4649 .EXIST => |err| return errnoBug(err),
4650 .NOTEMPTY => return error.DirNotEmpty,
4651 .ILSEQ => return error.BadPathName,
4652 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
4653 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4654 else => |err| return posix.unexpectedErrno(err),
4655 }
4656 },
4657 }
4658 }
4659}
4660
4661const dirRename = switch (native_os) {
4662 .windows => dirRenameWindows,
4663 .wasi => dirRenameWasi,
4664 else => dirRenamePosix,
4665};
4666
4667fn dirRenameWindows(
4668 userdata: ?*anyopaque,
4669 old_dir: Dir,
4670 old_sub_path: []const u8,
4671 new_dir: Dir,
4672 new_sub_path: []const u8,
4673) Dir.RenameError!void {
4674 const w = windows;
4675 const t: *Threaded = @ptrCast(@alignCast(userdata));
4676 const current_thread = Thread.getCurrent(t);
4677
4678 const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path);
4679 const old_path_w = old_path_w_buf.span();
4680 const new_path_w_buf = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path);
4681 const new_path_w = new_path_w_buf.span();
4682 const replace_if_exists = true;
4683
4684 try current_thread.checkCancel();
4685
4686 const src_fd = w.OpenFile(old_path_w, .{
4687 .dir = old_dir.handle,
4688 .access_mask = .{
4689 .GENERIC = .{ .WRITE = true },
4690 .STANDARD = .{
4691 .RIGHTS = .{ .DELETE = true },
4692 .SYNCHRONIZE = true,
4693 },
4694 },
4695 .creation = .OPEN,
4696 .filter = .any, // This function is supposed to rename both files and directories.
4697 .follow_symlinks = false,
4698 }) catch |err| switch (err) {
4699 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
4700 else => |e| return e,
4701 };
4702 defer w.CloseHandle(src_fd);
4703
4704 var rc: w.NTSTATUS = undefined;
4705 // FileRenameInformationEx has varying levels of support:
4706 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
4707 // (INVALID_INFO_CLASS is returned if not supported)
4708 // - Requires the NTFS filesystem
4709 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
4710 // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1
4711 // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
4712 // (NOT_SUPPORTED is returned if a flag is unsupported)
4713 //
4714 // The strategy here is just to try using FileRenameInformationEx and fall back to
4715 // FileRenameInformation if the return value lets us know that some aspect of it is not supported.
4716 const need_fallback = need_fallback: {
4717 const rename_info: w.FILE.RENAME_INFORMATION = .init(.{
4718 .Flags = .{
4719 .REPLACE_IF_EXISTS = replace_if_exists,
4720 .POSIX_SEMANTICS = true,
4721 .IGNORE_READONLY_ATTRIBUTE = true,
4722 },
4723 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,
4724 .FileName = new_path_w,
4725 });
4726 var io_status_block: w.IO_STATUS_BLOCK = undefined;
4727 const rename_info_buf = rename_info.toBuffer();
4728 rc = w.ntdll.NtSetInformationFile(
4729 src_fd,
4730 &io_status_block,
4731 rename_info_buf.ptr,
4732 @intCast(rename_info_buf.len),
4733 .RenameEx,
4734 );
4735 switch (rc) {
4736 .SUCCESS => return,
4737 // The filesystem does not support FileDispositionInformationEx
4738 .INVALID_PARAMETER,
4739 // The operating system does not support FileDispositionInformationEx
4740 .INVALID_INFO_CLASS,
4741 // The operating system does not support one of the flags
4742 .NOT_SUPPORTED,
4743 => break :need_fallback true,
4744 // For all other statuses, fall down to the switch below to handle them.
4745 else => break :need_fallback false,
4746 }
4747 };
4748
4749 if (need_fallback) {
4750 const rename_info: w.FILE.RENAME_INFORMATION = .init(.{
4751 .Flags = .{ .REPLACE_IF_EXISTS = replace_if_exists },
4752 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,
4753 .FileName = new_path_w,
4754 });
4755 var io_status_block: w.IO_STATUS_BLOCK = undefined;
4756 const rename_info_buf = rename_info.toBuffer();
4757 rc = w.ntdll.NtSetInformationFile(
4758 src_fd,
4759 &io_status_block,
4760 rename_info_buf.ptr,
4761 @intCast(rename_info_buf.len),
4762 .Rename,
4763 );
4764 }
4765
4766 switch (rc) {
4767 .SUCCESS => {},
4768 .INVALID_HANDLE => |err| return w.statusBug(err),
4769 .INVALID_PARAMETER => |err| return w.statusBug(err),
4770 .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err),
4771 .ACCESS_DENIED => return error.AccessDenied,
4772 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
4773 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
4774 .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
4775 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
4776 .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
4777 .FILE_IS_A_DIRECTORY => return error.IsDir,
4778 .NOT_A_DIRECTORY => return error.NotDir,
4779 else => return w.unexpectedStatus(rc),
4780 }
4781}
4782
4783fn dirRenameWasi(
4784 userdata: ?*anyopaque,
4785 old_dir: Dir,
4786 old_sub_path: []const u8,
4787 new_dir: Dir,
4788 new_sub_path: []const u8,
4789) Dir.RenameError!void {
4790 if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path);
4791
4792 const t: *Threaded = @ptrCast(@alignCast(userdata));
4793 const current_thread = Thread.getCurrent(t);
4794
4795 try current_thread.beginSyscall();
4796 while (true) {
4797 switch (std.os.wasi.path_rename(old_dir.handle, old_sub_path.ptr, old_sub_path.len, new_dir.handle, new_sub_path.ptr, new_sub_path.len)) {
4798 .SUCCESS => return current_thread.endSyscall(),
4799 .INTR => {
4800 try current_thread.checkCancel();
4801 continue;
4802 },
4803 else => |e| {
4804 current_thread.endSyscall();
4805 switch (e) {
4806 .ACCES => return error.AccessDenied,
4807 .PERM => return error.PermissionDenied,
4808 .BUSY => return error.FileBusy,
4809 .DQUOT => return error.DiskQuota,
4810 .FAULT => |err| return errnoBug(err),
4811 .INVAL => |err| return errnoBug(err),
4812 .ISDIR => return error.IsDir,
4813 .LOOP => return error.SymLinkLoop,
4814 .MLINK => return error.LinkQuotaExceeded,
4815 .NAMETOOLONG => return error.NameTooLong,
4816 .NOENT => return error.FileNotFound,
4817 .NOTDIR => return error.NotDir,
4818 .NOMEM => return error.SystemResources,
4819 .NOSPC => return error.NoSpaceLeft,
4820 .EXIST => return error.PathAlreadyExists,
4821 .NOTEMPTY => return error.PathAlreadyExists,
4822 .ROFS => return error.ReadOnlyFileSystem,
4823 .XDEV => return error.RenameAcrossMountPoints,
4824 .NOTCAPABLE => return error.AccessDenied,
4825 .ILSEQ => return error.BadPathName,
4826 else => |err| return posix.unexpectedErrno(err),
4827 }
4828 },
4829 }
4830 }
4831}
4832
4833fn dirRenamePosix(
4834 userdata: ?*anyopaque,
4835 old_dir: Dir,
4836 old_sub_path: []const u8,
4837 new_dir: Dir,
4838 new_sub_path: []const u8,
4839) Dir.RenameError!void {
4840 const t: *Threaded = @ptrCast(@alignCast(userdata));
4841 const current_thread = Thread.getCurrent(t);
4842
4843 var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
4844 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
4845
4846 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
4847 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
4848
4849 try current_thread.beginSyscall();
4850 while (true) {
4851 switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) {
4852 .SUCCESS => return current_thread.endSyscall(),
4853 .INTR => {
4854 try current_thread.checkCancel();
4855 continue;
4856 },
4857 else => |e| {
4858 current_thread.endSyscall();
4859 switch (e) {
4860 .ACCES => return error.AccessDenied,
4861 .PERM => return error.PermissionDenied,
4862 .BUSY => return error.FileBusy,
4863 .DQUOT => return error.DiskQuota,
4864 .FAULT => |err| return errnoBug(err),
4865 .INVAL => |err| return errnoBug(err),
4866 .ISDIR => return error.IsDir,
4867 .LOOP => return error.SymLinkLoop,
4868 .MLINK => return error.LinkQuotaExceeded,
4869 .NAMETOOLONG => return error.NameTooLong,
4870 .NOENT => return error.FileNotFound,
4871 .NOTDIR => return error.NotDir,
4872 .NOMEM => return error.SystemResources,
4873 .NOSPC => return error.NoSpaceLeft,
4874 .EXIST => return error.PathAlreadyExists,
4875 .NOTEMPTY => return error.PathAlreadyExists,
4876 .ROFS => return error.ReadOnlyFileSystem,
4877 .XDEV => return error.RenameAcrossMountPoints,
4878 .ILSEQ => return error.BadPathName,
4879 else => |err| return posix.unexpectedErrno(err),
4880 }
4881 },
4882 }
4883 }
4884}
4885
4886const dirSymLink = switch (native_os) {
4887 .windows => dirSymLinkWindows,
4888 .wasi => dirSymLinkWasi,
4889 else => dirSymLinkPosix,
4890};
4891
4892fn dirSymLinkWindows(
4893 userdata: ?*anyopaque,
4894 dir: Dir,
4895 target_path: []const u8,
4896 sym_link_path: []const u8,
4897 flags: Dir.SymLinkFlags,
4898) Dir.SymLinkError!void {
4899 const t: *Threaded = @ptrCast(@alignCast(userdata));
4900 const current_thread = Thread.getCurrent(t);
4901 const w = windows;
4902
4903 try current_thread.checkCancel();
4904
4905 // Target path does not use sliceToPrefixedFileW because certain paths
4906 // are handled differently when creating a symlink than they would be
4907 // when converting to an NT namespaced path. CreateSymbolicLink in
4908 // symLinkW will handle the necessary conversion.
4909 var target_path_w: w.PathSpace = undefined;
4910 target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path);
4911 target_path_w.data[target_path_w.len] = 0;
4912 // However, we need to canonicalize any path separators to `\`, since if
4913 // the target path is relative, then it must use `\` as the path separator.
4914 std.mem.replaceScalar(
4915 u16,
4916 target_path_w.data[0..target_path_w.len],
4917 std.mem.nativeToLittle(u16, '/'),
4918 std.mem.nativeToLittle(u16, '\\'),
4919 );
4920
4921 const sym_link_path_w = try w.sliceToPrefixedFileW(dir.handle, sym_link_path);
4922
4923 const SYMLINK_DATA = extern struct {
4924 ReparseTag: w.IO_REPARSE_TAG,
4925 ReparseDataLength: w.USHORT,
4926 Reserved: w.USHORT,
4927 SubstituteNameOffset: w.USHORT,
4928 SubstituteNameLength: w.USHORT,
4929 PrintNameOffset: w.USHORT,
4930 PrintNameLength: w.USHORT,
4931 Flags: w.ULONG,
4932 };
4933
4934 const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{
4935 .access_mask = .{
4936 .GENERIC = .{ .READ = true, .WRITE = true },
4937 .STANDARD = .{ .SYNCHRONIZE = true },
4938 },
4939 .dir = dir.handle,
4940 .creation = .CREATE,
4941 .filter = if (flags.is_directory) .dir_only else .non_directory_only,
4942 }) catch |err| switch (err) {
4943 error.IsDir => return error.PathAlreadyExists,
4944 error.NotDir => return error.Unexpected,
4945 error.WouldBlock => return error.Unexpected,
4946 error.PipeBusy => return error.Unexpected,
4947 error.NoDevice => return error.Unexpected,
4948 error.AntivirusInterference => return error.Unexpected,
4949 else => |e| return e,
4950 };
4951 defer w.CloseHandle(symlink_handle);
4952
4953 // Relevant portions of the documentation:
4954 // > Relative links are specified using the following conventions:
4955 // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32".
4956 // > - Current working directory–relative—for example, if the current working directory is
4957 // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt".
4958 // > Note: If you specify a current working directory–relative link, it is created as an absolute
4959 // > link, due to the way the current working directory is processed based on the user and the thread.
4960 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
4961 var is_target_absolute = false;
4962 const final_target_path = target_path: {
4963 if (w.hasCommonNtPrefix(u16, target_path_w.span())) {
4964 // Already an NT path, no need to do anything to it
4965 break :target_path target_path_w.span();
4966 } else {
4967 switch (w.getWin32PathType(u16, target_path_w.span())) {
4968 // Rooted paths need to avoid getting put through wToPrefixedFileW
4969 // (and they are treated as relative in this context)
4970 // Note: It seems that rooted paths in symbolic links are relative to
4971 // the drive that the symbolic exists on, not to the CWD's drive.
4972 // So, if the symlink is on C:\ and the CWD is on D:\,
4973 // it will still resolve the path relative to the root of
4974 // the C:\ drive.
4975 .rooted => break :target_path target_path_w.span(),
4976 // Keep relative paths relative, but anything else needs to get NT-prefixed.
4977 else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path_w.span()))
4978 break :target_path target_path_w.span(),
4979 }
4980 }
4981 var prefixed_target_path = try w.wToPrefixedFileW(dir.handle, target_path_w.span());
4982 // We do this after prefixing to ensure that drive-relative paths are treated as absolute
4983 is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span());
4984 break :target_path prefixed_target_path.span();
4985 };
4986
4987 // prepare reparse data buffer
4988 var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
4989 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;
4990 const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2;
4991 const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path);
4992 const symlink_data = SYMLINK_DATA{
4993 .ReparseTag = .SYMLINK,
4994 .ReparseDataLength = @intCast(buf_len - header_len),
4995 .Reserved = 0,
4996 .SubstituteNameOffset = @intCast(final_target_path.len * 2),
4997 .SubstituteNameLength = @intCast(final_target_path.len * 2),
4998 .PrintNameOffset = 0,
4999 .PrintNameLength = @intCast(final_target_path.len * 2),
5000 .Flags = if (!target_is_absolute) w.SYMLINK_FLAG_RELATIVE else 0,
5001 };
5002
5003 @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data));
5004 @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
5005 const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2;
5006 @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
5007 const rc = w.DeviceIoControl(symlink_handle, w.FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] });
5008 switch (rc) {
5009 .SUCCESS => {},
5010 .PRIVILEGE_NOT_HELD => return error.PermissionDenied,
5011 .ACCESS_DENIED => return error.AccessDenied,
5012 .INVALID_DEVICE_REQUEST => return error.FileSystem,
5013 else => return windows.unexpectedStatus(rc),
5014 }
5015}
5016
5017fn dirSymLinkWasi(
5018 userdata: ?*anyopaque,
5019 dir: Dir,
5020 target_path: []const u8,
5021 sym_link_path: []const u8,
5022 flags: Dir.SymLinkFlags,
5023) Dir.SymLinkError!void {
5024 if (builtin.link_libc) return dirSymLinkPosix(userdata, dir, target_path, sym_link_path, flags);
5025
5026 const t: *Threaded = @ptrCast(@alignCast(userdata));
5027 const current_thread = Thread.getCurrent(t);
5028
5029 try current_thread.beginSyscall();
5030 while (true) {
5031 switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) {
5032 .SUCCESS => return current_thread.endSyscall(),
5033 .INTR => {
5034 try current_thread.checkCancel();
5035 continue;
5036 },
5037 else => |e| {
5038 current_thread.endSyscall();
5039 switch (e) {
5040 .FAULT => |err| return errnoBug(err),
5041 .INVAL => |err| return errnoBug(err),
5042 .BADF => |err| return errnoBug(err),
5043 .ACCES => return error.AccessDenied,
5044 .PERM => return error.PermissionDenied,
5045 .DQUOT => return error.DiskQuota,
5046 .EXIST => return error.PathAlreadyExists,
5047 .IO => return error.FileSystem,
5048 .LOOP => return error.SymLinkLoop,
5049 .NAMETOOLONG => return error.NameTooLong,
5050 .NOENT => return error.FileNotFound,
5051 .NOTDIR => return error.NotDir,
5052 .NOMEM => return error.SystemResources,
5053 .NOSPC => return error.NoSpaceLeft,
5054 .ROFS => return error.ReadOnlyFileSystem,
5055 .NOTCAPABLE => return error.AccessDenied,
5056 .ILSEQ => return error.BadPathName,
5057 else => |err| return posix.unexpectedErrno(err),
5058 }
5059 },
5060 }
5061 }
5062}
5063
5064fn dirSymLinkPosix(
5065 userdata: ?*anyopaque,
5066 dir: Dir,
5067 target_path: []const u8,
5068 sym_link_path: []const u8,
5069 flags: Dir.SymLinkFlags,
5070) Dir.SymLinkError!void {
5071 _ = flags;
5072 const t: *Threaded = @ptrCast(@alignCast(userdata));
5073 const current_thread = Thread.getCurrent(t);
5074
5075 var target_path_buffer: [posix.PATH_MAX]u8 = undefined;
5076 var sym_link_path_buffer: [posix.PATH_MAX]u8 = undefined;
5077
5078 const target_path_posix = try pathToPosix(target_path, &target_path_buffer);
5079 const sym_link_path_posix = try pathToPosix(sym_link_path, &sym_link_path_buffer);
5080
5081 try current_thread.beginSyscall();
5082 while (true) {
5083 switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) {
5084 .SUCCESS => return current_thread.endSyscall(),
5085 .INTR => {
5086 try current_thread.checkCancel();
5087 continue;
5088 },
5089 else => |e| {
5090 current_thread.endSyscall();
5091 switch (e) {
5092 .FAULT => |err| return errnoBug(err),
5093 .INVAL => |err| return errnoBug(err),
5094 .ACCES => return error.AccessDenied,
5095 .PERM => return error.PermissionDenied,
5096 .DQUOT => return error.DiskQuota,
5097 .EXIST => return error.PathAlreadyExists,
5098 .IO => return error.FileSystem,
5099 .LOOP => return error.SymLinkLoop,
5100 .NAMETOOLONG => return error.NameTooLong,
5101 .NOENT => return error.FileNotFound,
5102 .NOTDIR => return error.NotDir,
5103 .NOMEM => return error.SystemResources,
5104 .NOSPC => return error.NoSpaceLeft,
5105 .ROFS => return error.ReadOnlyFileSystem,
5106 .ILSEQ => return error.BadPathName,
5107 else => |err| return posix.unexpectedErrno(err),
5108 }
5109 },
5110 }
5111 }
5112}
5113
5114const dirReadLink = switch (native_os) {
5115 .windows => dirReadLinkWindows,
5116 .wasi => dirReadLinkWasi,
5117 else => dirReadLinkPosix,
5118};
5119
5120fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
5121 const t: *Threaded = @ptrCast(@alignCast(userdata));
5122 const current_thread = Thread.getCurrent(t);
5123 const w = windows;
5124
5125 try current_thread.checkCancel();
5126
5127 var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
5128
5129 const result_w = try w.ReadLink(dir.handle, sub_path_w_buf.span(), &sub_path_w_buf.data);
5130
5131 const len = std.unicode.calcWtf8Len(result_w);
5132 if (len > buffer.len) return error.NameTooLong;
5133
5134 return std.unicode.wtf16LeToWtf8(buffer, result_w);
5135}
5136
5137fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
5138 if (builtin.link_libc) return dirReadLinkPosix(userdata, dir, sub_path, buffer);
5139
5140 const t: *Threaded = @ptrCast(@alignCast(userdata));
5141 const current_thread = Thread.getCurrent(t);
5142
5143 var n: usize = undefined;
5144 try current_thread.beginSyscall();
5145 while (true) {
5146 switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) {
5147 .SUCCESS => {
5148 current_thread.endSyscall();
5149 return n;
5150 },
5151 .INTR => {
5152 try current_thread.checkCancel();
5153 continue;
5154 },
5155 else => |e| {
5156 current_thread.endSyscall();
5157 switch (e) {
5158 .ACCES => return error.AccessDenied,
5159 .FAULT => |err| return errnoBug(err),
5160 .INVAL => return error.NotLink,
5161 .IO => return error.FileSystem,
5162 .LOOP => return error.SymLinkLoop,
5163 .NAMETOOLONG => return error.NameTooLong,
5164 .NOENT => return error.FileNotFound,
5165 .NOMEM => return error.SystemResources,
5166 .NOTDIR => return error.NotDir,
5167 .NOTCAPABLE => return error.AccessDenied,
5168 .ILSEQ => return error.BadPathName,
5169 else => |err| return posix.unexpectedErrno(err),
5170 }
5171 },
5172 }
5173 }
5174}
5175
5176fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize {
5177 const t: *Threaded = @ptrCast(@alignCast(userdata));
5178 const current_thread = Thread.getCurrent(t);
5179
5180 var sub_path_buffer: [posix.PATH_MAX]u8 = undefined;
5181 const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer);
5182
5183 try current_thread.beginSyscall();
5184 while (true) {
5185 const rc = posix.system.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);
5186 switch (posix.errno(rc)) {
5187 .SUCCESS => {
5188 current_thread.endSyscall();
5189 const len: usize = @bitCast(rc);
5190 return len;
5191 },
5192 .INTR => {
5193 try current_thread.checkCancel();
5194 continue;
5195 },
5196 else => |e| {
5197 current_thread.endSyscall();
5198 switch (e) {
5199 .ACCES => return error.AccessDenied,
5200 .FAULT => |err| return errnoBug(err),
5201 .INVAL => return error.NotLink,
5202 .IO => return error.FileSystem,
5203 .LOOP => return error.SymLinkLoop,
5204 .NAMETOOLONG => return error.NameTooLong,
5205 .NOENT => return error.FileNotFound,
5206 .NOMEM => return error.SystemResources,
5207 .NOTDIR => return error.NotDir,
5208 .ILSEQ => return error.BadPathName,
5209 else => |err| return posix.unexpectedErrno(err),
5210 }
5211 },
5212 }
5213 }
5214}
5215
5216const dirSetPermissions = switch (native_os) {
5217 .windows => dirSetPermissionsWindows,
5218 else => dirSetPermissionsPosix,
5219};
5220
5221fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void {
5222 const t: *Threaded = @ptrCast(@alignCast(userdata));
5223 _ = t;
5224 _ = dir;
5225 _ = permissions;
5226 @panic("TODO implement dirSetPermissionsWindows");
5227}
5228
5229fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void {
5230 if (@sizeOf(Dir.Permissions) == 0) return;
5231 const t: *Threaded = @ptrCast(@alignCast(userdata));
5232 const current_thread = Thread.getCurrent(t);
5233 return setPermissionsPosix(current_thread, dir.handle, permissions.toMode());
5234}
5235
5236fn dirSetFilePermissions(
5237 userdata: ?*anyopaque,
5238 dir: Dir,
5239 sub_path: []const u8,
5240 permissions: Dir.Permissions,
5241 options: Dir.SetFilePermissionsOptions,
5242) Dir.SetFilePermissionsError!void {
5243 if (@sizeOf(Dir.Permissions) == 0) return;
5244 if (is_windows) @panic("TODO implement dirSetFilePermissions windows");
5245 const t: *Threaded = @ptrCast(@alignCast(userdata));
5246 const current_thread = Thread.getCurrent(t);
5247
5248 var path_buffer: [posix.PATH_MAX]u8 = undefined;
5249 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
5250
5251 const mode = permissions.toMode();
5252 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
5253
5254 return posixFchmodat(t, current_thread, dir.handle, sub_path_posix, mode, flags);
5255}
5256
5257fn posixFchmodat(
5258 t: *Threaded,
5259 current_thread: *Thread,
5260 dir_fd: posix.fd_t,
5261 path: [*:0]const u8,
5262 mode: posix.mode_t,
5263 flags: u32,
5264) Dir.SetFilePermissionsError!void {
5265 // No special handling for linux is needed if we can use the libc fallback
5266 // or `flags` is empty. Glibc only added the fallback in 2.32.
5267 if (have_fchmodat_flags or flags == 0) {
5268 try current_thread.beginSyscall();
5269 while (true) {
5270 const rc = if (have_fchmodat_flags or builtin.link_libc)
5271 posix.system.fchmodat(dir_fd, path, mode, flags)
5272 else
5273 posix.system.fchmodat(dir_fd, path, mode);
5274 switch (posix.errno(rc)) {
5275 .SUCCESS => return current_thread.endSyscall(),
5276 .INTR => {
5277 try current_thread.checkCancel();
5278 continue;
5279 },
5280 else => |e| {
5281 current_thread.endSyscall();
5282 switch (e) {
5283 .BADF => |err| return errnoBug(err),
5284 .FAULT => |err| return errnoBug(err),
5285 .INVAL => |err| return errnoBug(err),
5286 .ACCES => return error.AccessDenied,
5287 .IO => return error.InputOutput,
5288 .LOOP => return error.SymLinkLoop,
5289 .MFILE => return error.ProcessFdQuotaExceeded,
5290 .NAMETOOLONG => return error.NameTooLong,
5291 .NFILE => return error.SystemFdQuotaExceeded,
5292 .NOENT => return error.FileNotFound,
5293 .NOTDIR => return error.FileNotFound,
5294 .NOMEM => return error.SystemResources,
5295 .OPNOTSUPP => return error.OperationUnsupported,
5296 .PERM => return error.PermissionDenied,
5297 .ROFS => return error.ReadOnlyFileSystem,
5298 else => |err| return posix.unexpectedErrno(err),
5299 }
5300 },
5301 }
5302 }
5303 }
5304
5305 if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled)
5306 return fchmodatFallback(current_thread, dir_fd, path, mode);
5307
5308 comptime assert(native_os == .linux);
5309
5310 try current_thread.beginSyscall();
5311 while (true) {
5312 switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) {
5313 .SUCCESS => return current_thread.endSyscall(),
5314 .INTR => {
5315 try current_thread.checkCancel();
5316 continue;
5317 },
5318 else => |e| {
5319 current_thread.endSyscall();
5320 switch (e) {
5321 .BADF => |err| return errnoBug(err),
5322 .FAULT => |err| return errnoBug(err),
5323 .INVAL => |err| return errnoBug(err),
5324 .ACCES => return error.AccessDenied,
5325 .IO => return error.InputOutput,
5326 .LOOP => return error.SymLinkLoop,
5327 .NOENT => return error.FileNotFound,
5328 .NOMEM => return error.SystemResources,
5329 .NOTDIR => return error.FileNotFound,
5330 .OPNOTSUPP => return error.OperationUnsupported,
5331 .PERM => return error.PermissionDenied,
5332 .ROFS => return error.ReadOnlyFileSystem,
5333 .NOSYS => {
5334 @atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic);
5335 return fchmodatFallback(current_thread, dir_fd, path, mode);
5336 },
5337 else => |err| return posix.unexpectedErrno(err),
5338 }
5339 },
5340 }
5341 }
5342}
5343
5344fn fchmodatFallback(
5345 current_thread: *Thread,
5346 dir_fd: posix.fd_t,
5347 path: [*:0]const u8,
5348 mode: posix.mode_t,
5349) Dir.SetFilePermissionsError!void {
5350 comptime assert(native_os == .linux);
5351 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
5352 .{ .major = 30, .minor = 0, .patch = 0 }
5353 else
5354 .{ .major = 2, .minor = 28, .patch = 0 });
5355 const sys = if (use_c) std.c else std.os.linux;
5356
5357 // Fallback to changing permissions using procfs:
5358 //
5359 // 1. Open `path` as a `PATH` descriptor.
5360 // 2. Stat the fd and check if it isn't a symbolic link.
5361 // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`.
5362 // 4. Pass the procfs path to `chmod` with the `mode`.
5363 try current_thread.beginSyscall();
5364 const path_fd: posix.fd_t = while (true) {
5365 const rc = posix.system.openat(dir_fd, path, .{
5366 .PATH = true,
5367 .NOFOLLOW = true,
5368 .CLOEXEC = true,
5369 }, @as(posix.mode_t, 0));
5370 switch (posix.errno(rc)) {
5371 .SUCCESS => {
5372 current_thread.endSyscall();
5373 break @intCast(rc);
5374 },
5375 .INTR => {
5376 try current_thread.checkCancel();
5377 continue;
5378 },
5379 else => |e| {
5380 current_thread.endSyscall();
5381 switch (e) {
5382 .FAULT => |err| return errnoBug(err),
5383 .INVAL => |err| return errnoBug(err),
5384 .ACCES => return error.AccessDenied,
5385 .PERM => return error.PermissionDenied,
5386 .LOOP => return error.SymLinkLoop,
5387 .MFILE => return error.ProcessFdQuotaExceeded,
5388 .NAMETOOLONG => return error.NameTooLong,
5389 .NFILE => return error.SystemFdQuotaExceeded,
5390 .NOENT => return error.FileNotFound,
5391 .NOMEM => return error.SystemResources,
5392 else => |err| return posix.unexpectedErrno(err),
5393 }
5394 },
5395 }
5396 };
5397 defer posix.close(path_fd);
5398
5399 try current_thread.beginSyscall();
5400 const path_mode = while (true) {
5401 var statx = std.mem.zeroes(std.os.linux.Statx);
5402 switch (sys.errno(sys.statx(path_fd, "", posix.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
5403 .SUCCESS => {
5404 current_thread.endSyscall();
5405 if (!statx.mask.TYPE) return error.Unexpected;
5406 break statx.mode;
5407 },
5408 .INTR => {
5409 try current_thread.checkCancel();
5410 continue;
5411 },
5412 else => |e| {
5413 current_thread.endSyscall();
5414 switch (e) {
5415 .ACCES => return error.AccessDenied,
5416 .LOOP => return error.SymLinkLoop,
5417 .NOMEM => return error.SystemResources,
5418 else => |err| return posix.unexpectedErrno(err),
5419 }
5420 },
5421 }
5422 };
5423
5424 // Even though we only wanted TYPE, the kernel can still fill in the additional bits.
5425 if ((path_mode & posix.S.IFMT) == posix.S.IFLNK)
5426 return error.OperationUnsupported;
5427
5428 var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
5429 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, "/proc/self/fd/{d}", .{path_fd}, 0) catch unreachable;
5430 try current_thread.beginSyscall();
5431 while (true) {
5432 switch (posix.errno(posix.system.chmod(proc_path, mode))) {
5433 .SUCCESS => return current_thread.endSyscall(),
5434 .INTR => {
5435 try current_thread.checkCancel();
5436 continue;
5437 },
5438 else => |e| {
5439 current_thread.endSyscall();
5440 switch (e) {
5441 .NOENT => return error.OperationUnsupported, // procfs not mounted.
5442 .BADF => |err| return errnoBug(err),
5443 .FAULT => |err| return errnoBug(err),
5444 .INVAL => |err| return errnoBug(err),
5445 .ACCES => return error.AccessDenied,
5446 .IO => return error.InputOutput,
5447 .LOOP => return error.SymLinkLoop,
5448 .NOMEM => return error.SystemResources,
5449 .NOTDIR => return error.FileNotFound,
5450 .PERM => return error.PermissionDenied,
5451 .ROFS => return error.ReadOnlyFileSystem,
5452 else => |err| return posix.unexpectedErrno(err),
5453 }
5454 },
5455 }
5456 }
5457}
5458
5459const dirSetOwner = switch (native_os) {
5460 .windows => dirSetOwnerUnsupported,
5461 else => dirSetOwnerPosix,
5462};
5463
5464fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void {
5465 _ = userdata;
5466 _ = dir;
5467 _ = owner;
5468 _ = group;
5469 return error.Unexpected;
5470}
5471
5472fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Dir, owner: ?File.Uid, group: ?File.Gid) Dir.SetOwnerError!void {
5473 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
5474 const t: *Threaded = @ptrCast(@alignCast(userdata));
5475 const current_thread = Thread.getCurrent(t);
5476 const uid = owner orelse ~@as(posix.uid_t, 0);
5477 const gid = group orelse ~@as(posix.gid_t, 0);
5478 return posixFchown(current_thread, dir.handle, uid, gid);
5479}
5480
5481fn posixFchown(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: posix.gid_t) File.SetOwnerError!void {
5482 comptime assert(have_fchown);
5483 try current_thread.beginSyscall();
5484 while (true) {
5485 switch (posix.errno(posix.system.fchown(fd, uid, gid))) {
5486 .SUCCESS => return current_thread.endSyscall(),
5487 .INTR => {
5488 try current_thread.checkCancel();
5489 continue;
5490 },
5491 else => |e| {
5492 current_thread.endSyscall();
5493 switch (e) {
5494 .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate`
5495 .FAULT => |err| return errnoBug(err),
5496 .INVAL => |err| return errnoBug(err),
5497 .ACCES => return error.AccessDenied,
5498 .IO => return error.InputOutput,
5499 .LOOP => return error.SymLinkLoop,
5500 .NOENT => return error.FileNotFound,
5501 .NOMEM => return error.SystemResources,
5502 .NOTDIR => return error.FileNotFound,
5503 .PERM => return error.PermissionDenied,
5504 .ROFS => return error.ReadOnlyFileSystem,
5505 else => |err| return posix.unexpectedErrno(err),
5506 }
5507 },
5508 }
5509 }
5510}
5511
5512fn dirSetFileOwner(
5513 userdata: ?*anyopaque,
5514 dir: Dir,
5515 sub_path: []const u8,
5516 owner: ?File.Uid,
5517 group: ?File.Gid,
5518 options: Dir.SetFileOwnerOptions,
5519) Dir.SetFileOwnerError!void {
5520 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
5521 const t: *Threaded = @ptrCast(@alignCast(userdata));
5522 const current_thread = Thread.getCurrent(t);
5523
5524 var path_buffer: [posix.PATH_MAX]u8 = undefined;
5525 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
5526
5527 _ = current_thread;
5528 _ = dir;
5529 _ = sub_path_posix;
5530 _ = owner;
5531 _ = group;
5532 _ = options;
5533 @panic("TODO implement dirSetFileOwner");
5534}
5535
5536const fileSync = switch (native_os) {
5537 .windows => fileSyncWindows,
5538 .wasi => fileSyncWasi,
5539 else => fileSyncPosix,
5540};
5541
5542fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void {
5543 const t: *Threaded = @ptrCast(@alignCast(userdata));
5544 const current_thread = Thread.getCurrent(t);
5545
5546 try current_thread.checkCancel();
5547
5548 if (windows.kernel32.FlushFileBuffers(file.handle) != 0)
5549 return;
5550
5551 switch (windows.GetLastError()) {
5552 .SUCCESS => return,
5553 .INVALID_HANDLE => unreachable,
5554 .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time
5555 .UNEXP_NET_ERR => return error.InputOutput,
5556 else => |err| return windows.unexpectedError(err),
5557 }
5558}
5559
5560fn fileSyncPosix(userdata: ?*anyopaque, file: File) File.SyncError!void {
5561 const t: *Threaded = @ptrCast(@alignCast(userdata));
5562 const current_thread = Thread.getCurrent(t);
5563 try current_thread.beginSyscall();
5564 while (true) {
5565 switch (posix.errno(posix.system.fsync(file.handle))) {
5566 .SUCCESS => return current_thread.endSyscall(),
5567 .INTR => {
5568 try current_thread.checkCancel();
5569 continue;
5570 },
5571 else => |e| {
5572 current_thread.endSyscall();
5573 switch (e) {
5574 .BADF => |err| return errnoBug(err),
5575 .INVAL => |err| return errnoBug(err),
5576 .ROFS => |err| return errnoBug(err),
5577 .IO => return error.InputOutput,
5578 .NOSPC => return error.NoSpaceLeft,
5579 .DQUOT => return error.DiskQuota,
5580 else => |err| return posix.unexpectedErrno(err),
5581 }
5582 },
5583 }
5584 }
5585}
5586
5587fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void {
5588 const t: *Threaded = @ptrCast(@alignCast(userdata));
5589 const current_thread = Thread.getCurrent(t);
5590 try current_thread.beginSyscall();
5591 while (true) {
5592 switch (std.os.wasi.fd_sync(file.handle)) {
5593 .SUCCESS => return current_thread.endSyscall(),
5594 .INTR => {
5595 try current_thread.checkCancel();
5596 continue;
5597 },
5598 else => |e| {
5599 current_thread.endSyscall();
5600 switch (e) {
5601 .BADF => |err| return errnoBug(err),
5602 .INVAL => |err| return errnoBug(err),
5603 .ROFS => |err| return errnoBug(err),
5604 .IO => return error.InputOutput,
5605 .NOSPC => return error.NoSpaceLeft,
5606 .DQUOT => return error.DiskQuota,
5607 else => |err| return posix.unexpectedErrno(err),
5608 }
5609 },
5610 }
5611 }
5612}
5613
5614fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
5615 const t: *Threaded = @ptrCast(@alignCast(userdata));
5616 const current_thread = Thread.getCurrent(t);
5617 return isTty(current_thread, file);
5618}
5619
5620fn isTty(current_thread: *Thread, file: File) Io.Cancelable!bool {
5621 if (is_windows) {
5622 if (try isCygwinPty(current_thread, file)) return true;
5623 try current_thread.checkCancel();
5624 var out: windows.DWORD = undefined;
5625 return windows.kernel32.GetConsoleMode(file.handle, &out) != 0;
5626 }
5627
5628 if (builtin.link_libc) {
5629 try current_thread.beginSyscall();
5630 while (true) {
5631 const rc = posix.system.isatty(file.handle);
5632 switch (posix.errno(rc - 1)) {
5633 .SUCCESS => {
5634 current_thread.endSyscall();
5635 return true;
5636 },
5637 .INTR => {
5638 try current_thread.checkCancel();
5639 continue;
5640 },
5641 else => {
5642 current_thread.endSyscall();
5643 return false;
5644 },
5645 }
5646 }
5647 }
5648
5649 if (native_os == .wasi) {
5650 var statbuf: std.os.wasi.fdstat_t = undefined;
5651 const err = std.os.wasi.fd_fdstat_get(file.handle, &statbuf);
5652 if (err != .SUCCESS)
5653 return false;
5654
5655 // A tty is a character device that we can't seek or tell on.
5656 if (statbuf.fs_filetype != .CHARACTER_DEVICE)
5657 return false;
5658 if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL)
5659 return false;
5660
5661 return true;
5662 }
5663
5664 if (native_os == .linux) {
5665 const linux = std.os.linux;
5666 try current_thread.beginSyscall();
5667 while (true) {
5668 var wsz: posix.winsize = undefined;
5669 const fd: usize = @bitCast(@as(isize, file.handle));
5670 const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
5671 switch (linux.errno(rc)) {
5672 .SUCCESS => {
5673 current_thread.endSyscall();
5674 return true;
5675 },
5676 .INTR => {
5677 try current_thread.checkCancel();
5678 continue;
5679 },
5680 else => {
5681 current_thread.endSyscall();
5682 return false;
5683 },
5684 }
5685 }
5686 }
5687
5688 @compileError("unimplemented");
5689}
5690
5691fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void {
5692 const t: *Threaded = @ptrCast(@alignCast(userdata));
5693 const current_thread = Thread.getCurrent(t);
5694
5695 if (is_windows) {
5696 try current_thread.checkCancel();
5697
5698 // For Windows Terminal, VT Sequences processing is enabled by default.
5699 var original_console_mode: windows.DWORD = 0;
5700 if (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) != 0) {
5701 if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return;
5702
5703 // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default.
5704 // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/
5705 //
5706 // Note: In Microsoft's example for enabling virtual terminal processing, it
5707 // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well:
5708 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
5709 // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n)
5710 // to behave unexpectedly (the cursor moves down 1 row but remains on the same column).
5711 // Additionally, the default console mode in Windows Terminal does not have
5712 // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING`
5713 // we end up matching the mode of Windows Terminal.
5714 const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
5715 const console_mode = original_console_mode | requested_console_modes;
5716 try current_thread.checkCancel();
5717 if (windows.kernel32.SetConsoleMode(file.handle, console_mode) != 0) return;
5718 }
5719 if (try isCygwinPty(current_thread, file)) return;
5720 } else {
5721 if (try supportsAnsiEscapeCodes(current_thread, file)) return;
5722 }
5723 return error.NotTerminalDevice;
5724}
5725
5726fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
5727 const t: *Threaded = @ptrCast(@alignCast(userdata));
5728 const current_thread = Thread.getCurrent(t);
5729 return supportsAnsiEscapeCodes(current_thread, file);
5730}
5731
5732fn supportsAnsiEscapeCodes(current_thread: *Thread, file: File) Io.Cancelable!bool {
5733 if (is_windows) {
5734 try current_thread.checkCancel();
5735 var console_mode: windows.DWORD = 0;
5736 if (windows.kernel32.GetConsoleMode(file.handle, &console_mode) != 0) {
5737 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
5738 }
5739 return isCygwinPty(current_thread, file);
5740 }
5741
5742 if (native_os == .wasi) {
5743 // WASI sanitizes stdout when fd is a tty so ANSI escape codes will not
5744 // be interpreted as actual cursor commands, and stderr is always
5745 // sanitized.
5746 return false;
5747 }
5748
5749 if (try isTty(current_thread, file)) return true;
5750
5751 return false;
5752}
5753
5754fn isCygwinPty(current_thread: *Thread, file: File) Io.Cancelable!bool {
5755 if (!is_windows) return false;
5756
5757 const handle = file.handle;
5758
5759 // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats:
5760 // msys-[...]-ptyN-[...]
5761 // cygwin-[...]-ptyN-[...]
5762 //
5763 // Example: msys-1888ae32e00d56aa-pty0-to-master
5764
5765 // First, just check that the handle is a named pipe.
5766 // This allows us to avoid the more costly NtQueryInformationFile call
5767 // for handles that aren't named pipes.
5768 {
5769 try current_thread.checkCancel();
5770 var io_status: windows.IO_STATUS_BLOCK = undefined;
5771 var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined;
5772 const rc = windows.ntdll.NtQueryVolumeInformationFile(
5773 handle,
5774 &io_status,
5775 &device_info,
5776 @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),
5777 .Device,
5778 );
5779 switch (rc) {
5780 .SUCCESS => {},
5781 else => return false,
5782 }
5783 if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false;
5784 }
5785
5786 const name_bytes_offset = @offsetOf(windows.FILE.NAME_INFORMATION, "FileName");
5787 // `NAME_MAX` UTF-16 code units (2 bytes each)
5788 // This buffer may not be long enough to handle *all* possible paths
5789 // (PATH_MAX_WIDE would be necessary for that), but because we only care
5790 // about certain paths and we know they must be within a reasonable length,
5791 // we can use this smaller buffer and just return false on any error from
5792 // NtQueryInformationFile.
5793 const num_name_bytes = windows.MAX_PATH * 2;
5794 var name_info_bytes align(@alignOf(windows.FILE.NAME_INFORMATION)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes);
5795
5796 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
5797 try current_thread.checkCancel();
5798 const rc = windows.ntdll.NtQueryInformationFile(
5799 handle,
5800 &io_status_block,
5801 &name_info_bytes,
5802 @intCast(name_info_bytes.len),
5803 .Name,
5804 );
5805 switch (rc) {
5806 .SUCCESS => {},
5807 .INVALID_PARAMETER => unreachable,
5808 else => return false,
5809 }
5810
5811 const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes);
5812 const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength];
5813 const name_wide = std.mem.bytesAsSlice(u16, name_bytes);
5814 // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master
5815 return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or
5816 std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and
5817 std.mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null;
5818}
5819
5820fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthError!void {
5821 const t: *Threaded = @ptrCast(@alignCast(userdata));
5822 const current_thread = Thread.getCurrent(t);
5823
5824 const signed_len: i64 = @bitCast(length);
5825 if (signed_len < 0) return error.FileTooBig; // Avoid ambiguous EINVAL errors.
5826
5827 if (is_windows) {
5828 try current_thread.checkCancel();
5829
5830 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
5831 const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{
5832 .EndOfFile = signed_len,
5833 };
5834
5835 const status = windows.ntdll.NtSetInformationFile(
5836 file.handle,
5837 &io_status_block,
5838 &eof_info,
5839 @sizeOf(windows.FILE.END_OF_FILE_INFORMATION),
5840 .EndOfFile,
5841 );
5842 switch (status) {
5843 .SUCCESS => return,
5844 .INVALID_HANDLE => |err| return windows.statusBug(err), // Handle not open for writing.
5845 .ACCESS_DENIED => return error.AccessDenied,
5846 .USER_MAPPED_FILE => return error.AccessDenied,
5847 .INVALID_PARAMETER => return error.FileTooBig,
5848 else => return windows.unexpectedStatus(status),
5849 }
5850 }
5851
5852 if (native_os == .wasi and !builtin.link_libc) {
5853 try current_thread.beginSyscall();
5854 while (true) {
5855 switch (std.os.wasi.fd_filestat_set_size(file.handle, length)) {
5856 .SUCCESS => return current_thread.endSyscall(),
5857 .INTR => {
5858 try current_thread.checkCancel();
5859 continue;
5860 },
5861 else => |e| {
5862 current_thread.endSyscall();
5863 switch (e) {
5864 .FBIG => return error.FileTooBig,
5865 .IO => return error.InputOutput,
5866 .PERM => return error.PermissionDenied,
5867 .TXTBSY => return error.FileBusy,
5868 .BADF => |err| return errnoBug(err), // Handle not open for writing
5869 .INVAL => return error.NonResizable,
5870 .NOTCAPABLE => return error.AccessDenied,
5871 else => |err| return posix.unexpectedErrno(err),
5872 }
5873 },
5874 }
5875 }
5876 }
5877
5878 try current_thread.beginSyscall();
5879 while (true) {
5880 switch (posix.errno(ftruncate_sym(file.handle, signed_len))) {
5881 .SUCCESS => return current_thread.endSyscall(),
5882 .INTR => {
5883 try current_thread.checkCancel();
5884 continue;
5885 },
5886 else => |e| {
5887 current_thread.endSyscall();
5888 switch (e) {
5889 .FBIG => return error.FileTooBig,
5890 .IO => return error.InputOutput,
5891 .PERM => return error.PermissionDenied,
5892 .TXTBSY => return error.FileBusy,
5893 .BADF => |err| return errnoBug(err), // Handle not open for writing.
5894 .INVAL => return error.NonResizable, // This is returned for /dev/null for example.
5895 else => |err| return posix.unexpectedErrno(err),
5896 }
5897 },
5898 }
5899 }
5900}
5901
5902fn fileSetOwner(userdata: ?*anyopaque, file: File, owner: ?File.Uid, group: ?File.Gid) File.SetOwnerError!void {
5903 if (!have_fchown) return error.Unexpected; // Unsupported OS, don't call this function.
5904 const t: *Threaded = @ptrCast(@alignCast(userdata));
5905 const current_thread = Thread.getCurrent(t);
5906 const uid = owner orelse ~@as(posix.uid_t, 0);
5907 const gid = group orelse ~@as(posix.gid_t, 0);
5908 return posixFchown(current_thread, file.handle, uid, gid);
5909}
5910
5911fn fileSetPermissions(userdata: ?*anyopaque, file: File, permissions: File.Permissions) File.SetPermissionsError!void {
5912 if (@sizeOf(File.Permissions) == 0) return;
5913 const t: *Threaded = @ptrCast(@alignCast(userdata));
5914 const current_thread = Thread.getCurrent(t);
5915 switch (native_os) {
5916 .windows => {
5917 try current_thread.checkCancel();
5918 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
5919 const info: windows.FILE.BASIC_INFORMATION = .{
5920 .CreationTime = 0,
5921 .LastAccessTime = 0,
5922 .LastWriteTime = 0,
5923 .ChangeTime = 0,
5924 .FileAttributes = permissions.toAttributes(),
5925 };
5926 const status = windows.ntdll.NtSetInformationFile(
5927 file.handle,
5928 &io_status_block,
5929 &info,
5930 @sizeOf(windows.FILE.BASIC_INFORMATION),
5931 .Basic,
5932 );
5933 switch (status) {
5934 .SUCCESS => return,
5935 .INVALID_HANDLE => |err| return windows.statusBug(err),
5936 .ACCESS_DENIED => return error.AccessDenied,
5937 else => return windows.unexpectedStatus(status),
5938 }
5939 },
5940 .wasi => return error.Unexpected, // Unsupported OS.
5941 else => return setPermissionsPosix(current_thread, file.handle, permissions.toMode()),
5942 }
5943}
5944
5945fn setPermissionsPosix(current_thread: *Thread, fd: posix.fd_t, mode: posix.mode_t) File.SetPermissionsError!void {
5946 comptime assert(have_fchmod);
5947 try current_thread.beginSyscall();
5948 while (true) {
5949 switch (posix.errno(posix.system.fchmod(fd, mode))) {
5950 .SUCCESS => return current_thread.endSyscall(),
5951 .INTR => {
5952 try current_thread.checkCancel();
5953 continue;
5954 },
5955 else => |e| {
5956 current_thread.endSyscall();
5957 switch (e) {
5958 .BADF => |err| return errnoBug(err),
5959 .FAULT => |err| return errnoBug(err),
5960 .INVAL => |err| return errnoBug(err),
5961 .ACCES => return error.AccessDenied,
5962 .IO => return error.InputOutput,
5963 .LOOP => return error.SymLinkLoop,
5964 .NOENT => return error.FileNotFound,
5965 .NOMEM => return error.SystemResources,
5966 .NOTDIR => return error.FileNotFound,
5967 .PERM => return error.PermissionDenied,
5968 .ROFS => return error.ReadOnlyFileSystem,
5969 else => |err| return posix.unexpectedErrno(err),
5970 }
5971 },
5972 }
5973 }
5974}
5975
5976fn dirSetTimestamps(
5977 userdata: ?*anyopaque,
5978 dir: Dir,
5979 sub_path: []const u8,
5980 last_accessed: Io.Timestamp,
5981 last_modified: Io.Timestamp,
5982 options: Dir.SetTimestampsOptions,
5983) Dir.SetTimestampsError!void {
5984 const t: *Threaded = @ptrCast(@alignCast(userdata));
5985 const current_thread = Thread.getCurrent(t);
5986
5987 if (is_windows) {
5988 @panic("TODO implement dirSetTimestamps windows");
5989 }
5990
5991 if (native_os == .wasi and !builtin.link_libc) {
5992 @panic("TODO implement dirSetTimestamps wasi");
5993 }
5994
5995 const times: [2]posix.timespec = .{
5996 timestampToPosix(last_accessed.nanoseconds),
5997 timestampToPosix(last_modified.nanoseconds),
5998 };
5999
6000 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
6001
6002 var path_buffer: [posix.PATH_MAX]u8 = undefined;
6003 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
6004
6005 try current_thread.beginSyscall();
6006 while (true) {
6007 switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, &times, flags))) {
6008 .SUCCESS => return current_thread.endSyscall(),
6009 .INTR => {
6010 try current_thread.checkCancel();
6011 continue;
6012 },
6013 else => |e| {
6014 current_thread.endSyscall();
6015 switch (e) {
6016 .ACCES => return error.AccessDenied,
6017 .PERM => return error.PermissionDenied,
6018 .BADF => |err| return errnoBug(err), // always a race condition
6019 .FAULT => |err| return errnoBug(err),
6020 .INVAL => |err| return errnoBug(err),
6021 .ROFS => return error.ReadOnlyFileSystem,
6022 else => |err| return posix.unexpectedErrno(err),
6023 }
6024 },
6025 }
6026 }
6027}
6028
6029fn dirSetTimestampsNow(
6030 userdata: ?*anyopaque,
6031 dir: Dir,
6032 sub_path: []const u8,
6033 options: Dir.SetTimestampsOptions,
6034) Dir.SetTimestampsError!void {
6035 const t: *Threaded = @ptrCast(@alignCast(userdata));
6036 const current_thread = Thread.getCurrent(t);
6037
6038 if (is_windows) {
6039 @panic("TODO implement dirSetTimestampsNow windows");
6040 }
6041
6042 if (native_os == .wasi and !builtin.link_libc) {
6043 @panic("TODO implement dirSetTimestampsNow wasi");
6044 }
6045
6046 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
6047
6048 var path_buffer: [posix.PATH_MAX]u8 = undefined;
6049 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
6050
6051 try current_thread.beginSyscall();
6052 while (true) {
6053 switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, null, flags))) {
6054 .SUCCESS => return current_thread.endSyscall(),
6055 .INTR => {
6056 try current_thread.checkCancel();
6057 continue;
6058 },
6059 else => |e| {
6060 current_thread.endSyscall();
6061 switch (e) {
6062 .ACCES => return error.AccessDenied,
6063 .PERM => return error.PermissionDenied,
6064 .BADF => |err| return errnoBug(err), // always a race condition
6065 .FAULT => |err| return errnoBug(err),
6066 .INVAL => |err| return errnoBug(err),
6067 .ROFS => return error.ReadOnlyFileSystem,
6068 else => |err| return posix.unexpectedErrno(err),
6069 }
6070 },
6071 }
6072 }
6073}
6074
6075fn fileSetTimestamps(
6076 userdata: ?*anyopaque,
6077 file: File,
6078 last_accessed: Io.Timestamp,
6079 last_modified: Io.Timestamp,
6080) File.SetTimestampsError!void {
6081 const t: *Threaded = @ptrCast(@alignCast(userdata));
6082 const current_thread = Thread.getCurrent(t);
6083
6084 if (is_windows) {
6085 try current_thread.checkCancel();
6086
6087 const atime_ft = windows.nanoSecondsToFileTime(last_accessed);
6088 const mtime_ft = windows.nanoSecondsToFileTime(last_modified);
6089
6090 // https://github.com/ziglang/zig/issues/1840
6091 const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft);
6092 if (rc == 0) {
6093 switch (windows.GetLastError()) {
6094 else => |err| return windows.unexpectedError(err),
6095 }
6096 }
6097 return;
6098 }
6099
6100 const times: [2]posix.timespec = .{
6101 timestampToPosix(last_accessed.nanoseconds),
6102 timestampToPosix(last_modified.nanoseconds),
6103 };
6104
6105 if (native_os == .wasi and !builtin.link_libc) {
6106 const atim = times[0].toTimestamp();
6107 const mtim = times[1].toTimestamp();
6108 try current_thread.beginSyscall();
6109 while (true) {
6110 switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{
6111 .ATIM = true,
6112 .MTIM = true,
6113 })) {
6114 .SUCCESS => return current_thread.endSyscall(),
6115 .INTR => {
6116 try current_thread.checkCancel();
6117 continue;
6118 },
6119 else => |e| {
6120 current_thread.endSyscall();
6121 switch (e) {
6122 .ACCES => return error.AccessDenied,
6123 .PERM => return error.PermissionDenied,
6124 .BADF => |err| return errnoBug(err), // File descriptor use-after-free.
6125 .FAULT => |err| return errnoBug(err),
6126 .INVAL => |err| return errnoBug(err),
6127 .ROFS => return error.ReadOnlyFileSystem,
6128 else => |err| return posix.unexpectedErrno(err),
6129 }
6130 },
6131 }
6132 }
6133 }
6134
6135 try current_thread.beginSyscall();
6136 while (true) {
6137 switch (posix.errno(posix.system.futimens(file.handle, &times))) {
6138 .SUCCESS => return current_thread.endSyscall(),
6139 .INTR => {
6140 try current_thread.checkCancel();
6141 continue;
6142 },
6143 else => |e| {
6144 current_thread.endSyscall();
6145 switch (e) {
6146 .ACCES => return error.AccessDenied,
6147 .PERM => return error.PermissionDenied,
6148 .BADF => |err| return errnoBug(err), // always a race condition
6149 .FAULT => |err| return errnoBug(err),
6150 .INVAL => |err| return errnoBug(err),
6151 .ROFS => return error.ReadOnlyFileSystem,
6152 else => |err| return posix.unexpectedErrno(err),
6153 }
6154 },
6155 }
6156 }
6157}
6158
6159fn fileSetTimestampsNow(userdata: ?*anyopaque, file: File) File.SetTimestampsError!void {
6160 const t: *Threaded = @ptrCast(@alignCast(userdata));
6161 const current_thread = Thread.getCurrent(t);
6162
6163 if (is_windows) {
6164 @panic("TODO implement fileSetTimestampsNow windows");
6165 }
6166
6167 if (native_os == .wasi and !builtin.link_libc) {
6168 try current_thread.beginSyscall();
6169 while (true) {
6170 switch (std.os.wasi.fd_filestat_set_times(file.handle, 0, 0, .{
6171 .ATIM_NOW = true,
6172 .MTIM_NOW = true,
6173 })) {
6174 .SUCCESS => return current_thread.endSyscall(),
6175 .INTR => {
6176 try current_thread.checkCancel();
6177 continue;
6178 },
6179 else => |e| {
6180 current_thread.endSyscall();
6181 switch (e) {
6182 .ACCES => return error.AccessDenied,
6183 .PERM => return error.PermissionDenied,
6184 .BADF => |err| return errnoBug(err), // always a race condition
6185 .FAULT => |err| return errnoBug(err),
6186 .INVAL => |err| return errnoBug(err),
6187 .ROFS => return error.ReadOnlyFileSystem,
6188 else => |err| return posix.unexpectedErrno(err),
6189 }
6190 },
6191 }
6192 }
6193 }
6194
6195 try current_thread.beginSyscall();
6196 while (true) {
6197 switch (posix.errno(posix.system.futimens(file.handle, null))) {
6198 .SUCCESS => return current_thread.endSyscall(),
6199 .INTR => {
6200 try current_thread.checkCancel();
6201 continue;
6202 },
6203 else => |e| {
6204 current_thread.endSyscall();
6205 switch (e) {
6206 .ACCES => return error.AccessDenied,
6207 .PERM => return error.PermissionDenied,
6208 .BADF => |err| return errnoBug(err), // always a race condition
6209 .FAULT => |err| return errnoBug(err),
6210 .INVAL => |err| return errnoBug(err),
6211 .ROFS => return error.ReadOnlyFileSystem,
6212 else => |err| return posix.unexpectedErrno(err),
6213 }
6214 },
6215 }
6216 }
6217}
6218
6219const windows_lock_range_off: windows.LARGE_INTEGER = 0;
6220const windows_lock_range_len: windows.LARGE_INTEGER = 1;
6221
6222fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void {
6223 if (native_os == .wasi) return error.FileLocksUnsupported;
6224 const t: *Threaded = @ptrCast(@alignCast(userdata));
6225 const current_thread = Thread.getCurrent(t);
6226
6227 if (is_windows) {
6228 const exclusive = switch (lock) {
6229 .none => {
6230 // To match the non-Windows behavior, unlock
6231 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6232 const status = windows.ntdll.NtUnlockFile(
6233 file.handle,
6234 &io_status_block,
6235 &windows_lock_range_off,
6236 &windows_lock_range_len,
6237 0,
6238 );
6239 switch (status) {
6240 .SUCCESS => {},
6241 .RANGE_NOT_LOCKED => {},
6242 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6243 else => return windows.unexpectedStatus(status),
6244 }
6245 return;
6246 },
6247 .shared => false,
6248 .exclusive => true,
6249 };
6250 try current_thread.checkCancel();
6251 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6252 const status = windows.ntdll.NtLockFile(
6253 file.handle,
6254 null,
6255 null,
6256 null,
6257 &io_status_block,
6258 &windows_lock_range_off,
6259 &windows_lock_range_len,
6260 null,
6261 windows.FALSE,
6262 @intFromBool(exclusive),
6263 );
6264 switch (status) {
6265 .SUCCESS => return,
6266 .INSUFFICIENT_RESOURCES => return error.SystemResources,
6267 .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // passed FailImmediately=false
6268 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6269 else => return windows.unexpectedStatus(status),
6270 }
6271 }
6272
6273 const operation: i32 = switch (lock) {
6274 .none => posix.LOCK.UN,
6275 .shared => posix.LOCK.SH,
6276 .exclusive => posix.LOCK.EX,
6277 };
6278 try current_thread.beginSyscall();
6279 while (true) {
6280 switch (posix.errno(posix.system.flock(file.handle, operation))) {
6281 .SUCCESS => return current_thread.endSyscall(),
6282 .INTR => {
6283 try current_thread.checkCancel();
6284 continue;
6285 },
6286 else => |e| {
6287 current_thread.endSyscall();
6288 switch (e) {
6289 .BADF => |err| return errnoBug(err),
6290 .INVAL => |err| return errnoBug(err), // invalid parameters
6291 .NOLCK => return error.SystemResources,
6292 .AGAIN => |err| return errnoBug(err),
6293 .OPNOTSUPP => return error.FileLocksUnsupported,
6294 else => |err| return posix.unexpectedErrno(err),
6295 }
6296 },
6297 }
6298 }
6299}
6300
6301fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool {
6302 if (native_os == .wasi) return error.FileLocksUnsupported;
6303 const t: *Threaded = @ptrCast(@alignCast(userdata));
6304 const current_thread = Thread.getCurrent(t);
6305
6306 if (is_windows) {
6307 const exclusive = switch (lock) {
6308 .none => {
6309 // To match the non-Windows behavior, unlock
6310 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6311 const status = windows.ntdll.NtUnlockFile(
6312 file.handle,
6313 &io_status_block,
6314 &windows_lock_range_off,
6315 &windows_lock_range_len,
6316 0,
6317 );
6318 switch (status) {
6319 .SUCCESS => return true,
6320 .RANGE_NOT_LOCKED => return false,
6321 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6322 else => return windows.unexpectedStatus(status),
6323 }
6324 },
6325 .shared => false,
6326 .exclusive => true,
6327 };
6328 try current_thread.checkCancel();
6329 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6330 const status = windows.ntdll.NtLockFile(
6331 file.handle,
6332 null,
6333 null,
6334 null,
6335 &io_status_block,
6336 &windows_lock_range_off,
6337 &windows_lock_range_len,
6338 null,
6339 windows.TRUE,
6340 @intFromBool(exclusive),
6341 );
6342 switch (status) {
6343 .SUCCESS => return true,
6344 .INSUFFICIENT_RESOURCES => return error.SystemResources,
6345 .LOCK_NOT_GRANTED => return false,
6346 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6347 else => return windows.unexpectedStatus(status),
6348 }
6349 }
6350
6351 const operation: i32 = switch (lock) {
6352 .none => posix.LOCK.UN,
6353 .shared => posix.LOCK.SH | posix.LOCK.NB,
6354 .exclusive => posix.LOCK.EX | posix.LOCK.NB,
6355 };
6356 try current_thread.beginSyscall();
6357 while (true) {
6358 switch (posix.errno(posix.system.flock(file.handle, operation))) {
6359 .SUCCESS => {
6360 current_thread.endSyscall();
6361 return true;
6362 },
6363 .INTR => {
6364 try current_thread.checkCancel();
6365 continue;
6366 },
6367 .AGAIN => {
6368 current_thread.endSyscall();
6369 return false;
6370 },
6371 else => |e| {
6372 current_thread.endSyscall();
6373 switch (e) {
6374 .BADF => |err| return errnoBug(err),
6375 .INVAL => |err| return errnoBug(err), // invalid parameters
6376 .NOLCK => return error.SystemResources,
6377 .OPNOTSUPP => return error.FileLocksUnsupported,
6378 else => |err| return posix.unexpectedErrno(err),
6379 }
6380 },
6381 }
6382 }
6383}
6384
6385fn fileUnlock(userdata: ?*anyopaque, file: File) void {
6386 if (native_os == .wasi) return;
6387 const t: *Threaded = @ptrCast(@alignCast(userdata));
6388 _ = t;
6389
6390 if (is_windows) {
6391 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6392 const status = windows.ntdll.NtUnlockFile(
6393 file.handle,
6394 &io_status_block,
6395 &windows_lock_range_off,
6396 &windows_lock_range_len,
6397 0,
6398 );
6399 if (is_debug) switch (status) {
6400 .SUCCESS => {},
6401 .RANGE_NOT_LOCKED => unreachable, // Function asserts unlocked.
6402 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
6403 else => unreachable, // Resource deallocation must succeed.
6404 };
6405 return;
6406 }
6407
6408 while (true) {
6409 switch (posix.errno(posix.system.flock(file.handle, posix.LOCK.UN))) {
6410 .SUCCESS => return,
6411 .CANCELED, .INTR => continue,
6412 .AGAIN => return assert(!is_debug), // unlocking can't block
6413 .BADF => return assert(!is_debug), // File descriptor used after closed.
6414 .INVAL => return assert(!is_debug), // invalid parameters
6415 .NOLCK => return assert(!is_debug), // Resource deallocation.
6416 .OPNOTSUPP => return assert(!is_debug), // We already got the lock.
6417 else => return assert(!is_debug), // Resource deallocation must succeed.
6418 }
6419 }
6420}
6421
6422fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void {
6423 if (native_os == .wasi) return;
6424 const t: *Threaded = @ptrCast(@alignCast(userdata));
6425 const current_thread = Thread.getCurrent(t);
6426
6427 if (is_windows) {
6428 try current_thread.checkCancel();
6429 // On Windows it works like a semaphore + exclusivity flag. To
6430 // implement this function, we first obtain another lock in shared
6431 // mode. This changes the exclusivity flag, but increments the
6432 // semaphore to 2. So we follow up with an NtUnlockFile which
6433 // decrements the semaphore but does not modify the exclusivity flag.
6434 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
6435 switch (windows.ntdll.NtLockFile(
6436 file.handle,
6437 null,
6438 null,
6439 null,
6440 &io_status_block,
6441 &windows_lock_range_off,
6442 &windows_lock_range_len,
6443 null,
6444 windows.TRUE,
6445 windows.FALSE,
6446 )) {
6447 .SUCCESS => {},
6448 .INSUFFICIENT_RESOURCES => |err| return windows.statusBug(err),
6449 .LOCK_NOT_GRANTED => |err| return windows.statusBug(err), // File was not locked in exclusive mode.
6450 .ACCESS_VIOLATION => |err| return windows.statusBug(err), // bad io_status_block pointer
6451 else => |status| return windows.unexpectedStatus(status),
6452 }
6453 const status = windows.ntdll.NtUnlockFile(
6454 file.handle,
6455 &io_status_block,
6456 &windows_lock_range_off,
6457 &windows_lock_range_len,
6458 0,
6459 );
6460 if (is_debug) switch (status) {
6461 .SUCCESS => {},
6462 .RANGE_NOT_LOCKED => unreachable, // File was not locked.
6463 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
6464 else => unreachable, // Resource deallocation must succeed.
6465 };
6466 return;
6467 }
6468
6469 const operation = posix.LOCK.SH | posix.LOCK.NB;
6470
6471 try current_thread.beginSyscall();
6472 while (true) {
6473 switch (posix.errno(posix.system.flock(file.handle, operation))) {
6474 .SUCCESS => {
6475 current_thread.endSyscall();
6476 return;
6477 },
6478 .INTR => {
6479 try current_thread.checkCancel();
6480 continue;
6481 },
6482 else => |e| {
6483 current_thread.endSyscall();
6484 switch (e) {
6485 .AGAIN => |err| return errnoBug(err), // File was not locked in exclusive mode.
6486 .BADF => |err| return errnoBug(err),
6487 .INVAL => |err| return errnoBug(err), // invalid parameters
6488 .NOLCK => |err| return errnoBug(err), // Lock already obtained.
6489 .OPNOTSUPP => |err| return errnoBug(err), // Lock already obtained.
6490 else => |err| return posix.unexpectedErrno(err),
6491 }
6492 },
6493 }
6494 }
6495}
6496
6497fn dirOpenDirWasi(
6498 userdata: ?*anyopaque,
6499 dir: Dir,
6500 sub_path: []const u8,
6501 options: Dir.OpenOptions,
6502) Dir.OpenError!Dir {
6503 if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options);
6504 const t: *Threaded = @ptrCast(@alignCast(userdata));
6505 const current_thread = Thread.getCurrent(t);
6506 const wasi = std.os.wasi;
6507
6508 var base: std.os.wasi.rights_t = .{
6509 .FD_FILESTAT_GET = true,
6510 .FD_FDSTAT_SET_FLAGS = true,
6511 .FD_FILESTAT_SET_TIMES = true,
6512 };
6513 if (options.access_sub_paths) {
6514 base.FD_READDIR = true;
6515 base.PATH_CREATE_DIRECTORY = true;
6516 base.PATH_CREATE_FILE = true;
6517 base.PATH_LINK_SOURCE = true;
6518 base.PATH_LINK_TARGET = true;
6519 base.PATH_OPEN = true;
6520 base.PATH_READLINK = true;
6521 base.PATH_RENAME_SOURCE = true;
6522 base.PATH_RENAME_TARGET = true;
6523 base.PATH_FILESTAT_GET = true;
6524 base.PATH_FILESTAT_SET_SIZE = true;
6525 base.PATH_FILESTAT_SET_TIMES = true;
6526 base.PATH_SYMLINK = true;
6527 base.PATH_REMOVE_DIRECTORY = true;
6528 base.PATH_UNLINK_FILE = true;
6529 }
6530
6531 const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks };
6532 const oflags: wasi.oflags_t = .{ .DIRECTORY = true };
6533 const fdflags: wasi.fdflags_t = .{};
6534 var fd: posix.fd_t = undefined;
6535 try current_thread.beginSyscall();
6536 while (true) {
6537 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) {
6538 .SUCCESS => {
6539 current_thread.endSyscall();
6540 return .{ .handle = fd };
6541 },
6542 .INTR => {
6543 try current_thread.checkCancel();
6544 continue;
6545 },
6546 else => |e| {
6547 current_thread.endSyscall();
6548 switch (e) {
6549 .FAULT => |err| return errnoBug(err),
6550 .INVAL => return error.BadPathName,
6551 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
6552 .ACCES => return error.AccessDenied,
6553 .LOOP => return error.SymLinkLoop,
6554 .MFILE => return error.ProcessFdQuotaExceeded,
6555 .NAMETOOLONG => return error.NameTooLong,
6556 .NFILE => return error.SystemFdQuotaExceeded,
6557 .NODEV => return error.NoDevice,
6558 .NOENT => return error.FileNotFound,
6559 .NOMEM => return error.SystemResources,
6560 .NOTDIR => return error.NotDir,
6561 .PERM => return error.PermissionDenied,
6562 .BUSY => return error.DeviceBusy,
6563 .NOTCAPABLE => return error.AccessDenied,
6564 .ILSEQ => return error.BadPathName,
6565 else => |err| return posix.unexpectedErrno(err),
6566 }
6567 },
6568 }
6569 }
6570}
6571
6572fn dirHardLink(
6573 userdata: ?*anyopaque,
6574 old_dir: Dir,
6575 old_sub_path: []const u8,
6576 new_dir: Dir,
6577 new_sub_path: []const u8,
6578 options: Dir.HardLinkOptions,
6579) Dir.HardLinkError!void {
6580 if (is_windows) return error.OperationUnsupported;
6581 const t: *Threaded = @ptrCast(@alignCast(userdata));
6582 const current_thread = Thread.getCurrent(t);
6583
6584 if (native_os == .wasi and !builtin.link_libc) {
6585 const flags: std.os.wasi.lookupflags_t = .{
6586 .SYMLINK_FOLLOW = options.follow_symlinks,
6587 };
6588 try current_thread.beginSyscall();
6589 while (true) {
6590 switch (std.os.wasi.path_link(
6591 old_dir.handle,
6592 flags,
6593 old_sub_path.ptr,
6594 old_sub_path.len,
6595 new_dir.handle,
6596 new_sub_path.ptr,
6597 new_sub_path.len,
6598 )) {
6599 .SUCCESS => return current_thread.endSyscall(),
6600 .INTR => {
6601 try current_thread.checkCancel();
6602 continue;
6603 },
6604 else => |e| {
6605 current_thread.endSyscall();
6606 switch (e) {
6607 .ACCES => return error.AccessDenied,
6608 .DQUOT => return error.DiskQuota,
6609 .EXIST => return error.PathAlreadyExists,
6610 .FAULT => |err| return errnoBug(err),
6611 .IO => return error.HardwareFailure,
6612 .LOOP => return error.SymLinkLoop,
6613 .MLINK => return error.LinkQuotaExceeded,
6614 .NAMETOOLONG => return error.NameTooLong,
6615 .NOENT => return error.FileNotFound,
6616 .NOMEM => return error.SystemResources,
6617 .NOSPC => return error.NoSpaceLeft,
6618 .NOTDIR => return error.NotDir,
6619 .PERM => return error.PermissionDenied,
6620 .ROFS => return error.ReadOnlyFileSystem,
6621 .XDEV => return error.NotSameFileSystem,
6622 .INVAL => |err| return errnoBug(err),
6623 .ILSEQ => return error.BadPathName,
6624 else => |err| return posix.unexpectedErrno(err),
6625 }
6626 },
6627 }
6628 }
6629 }
6630
6631 var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
6632 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
6633
6634 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
6635 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
6636
6637 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
6638
6639 try current_thread.beginSyscall();
6640 while (true) {
6641 switch (posix.errno(posix.system.linkat(
6642 old_dir.handle,
6643 old_sub_path_posix,
6644 new_dir.handle,
6645 new_sub_path_posix,
6646 flags,
6647 ))) {
6648 .SUCCESS => return current_thread.endSyscall(),
6649 .INTR => {
6650 try current_thread.checkCancel();
6651 continue;
6652 },
6653 else => |e| {
6654 current_thread.endSyscall();
6655 switch (e) {
6656 .ACCES => return error.AccessDenied,
6657 .DQUOT => return error.DiskQuota,
6658 .EXIST => return error.PathAlreadyExists,
6659 .FAULT => |err| return errnoBug(err),
6660 .IO => return error.HardwareFailure,
6661 .LOOP => return error.SymLinkLoop,
6662 .MLINK => return error.LinkQuotaExceeded,
6663 .NAMETOOLONG => return error.NameTooLong,
6664 .NOENT => return error.FileNotFound,
6665 .NOMEM => return error.SystemResources,
6666 .NOSPC => return error.NoSpaceLeft,
6667 .NOTDIR => return error.NotDir,
6668 .PERM => return error.PermissionDenied,
6669 .ROFS => return error.ReadOnlyFileSystem,
6670 .XDEV => return error.NotSameFileSystem,
6671 .INVAL => |err| return errnoBug(err),
6672 .ILSEQ => return error.BadPathName,
6673 else => |err| return posix.unexpectedErrno(err),
6674 }
6675 },
6676 }
6677 }
6678}
6679
6680fn fileClose(userdata: ?*anyopaque, files: []const File) void {
6681 const t: *Threaded = @ptrCast(@alignCast(userdata));
6682 _ = t;
6683 for (files) |file| posix.close(file.handle);
6684}
6685
6686const fileReadStreaming = switch (native_os) {
6687 .windows => fileReadStreamingWindows,
6688 else => fileReadStreamingPosix,
6689};
6690
6691fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize {
6692 const t: *Threaded = @ptrCast(@alignCast(userdata));
6693 const current_thread = Thread.getCurrent(t);
6694
6695 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
6696 var i: usize = 0;
6697 for (data) |buf| {
6698 if (iovecs_buffer.len - i == 0) break;
6699 if (buf.len != 0) {
6700 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
6701 i += 1;
6702 }
6703 }
6704 if (i == 0) return 0;
6705 const dest = iovecs_buffer[0..i];
6706 assert(dest[0].len > 0);
6707
6708 if (native_os == .wasi and !builtin.link_libc) {
6709 try current_thread.beginSyscall();
6710 while (true) {
6711 var nread: usize = undefined;
6712 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
6713 .SUCCESS => {
6714 current_thread.endSyscall();
6715 return nread;
6716 },
6717 .INTR => {
6718 try current_thread.checkCancel();
6719 continue;
6720 },
6721 else => |e| {
6722 current_thread.endSyscall();
6723 switch (e) {
6724 .INVAL => |err| return errnoBug(err),
6725 .FAULT => |err| return errnoBug(err),
6726 .BADF => return error.NotOpenForReading, // File operation on directory.
6727 .IO => return error.InputOutput,
6728 .ISDIR => return error.IsDir,
6729 .NOBUFS => return error.SystemResources,
6730 .NOMEM => return error.SystemResources,
6731 .NOTCONN => return error.SocketUnconnected,
6732 .CONNRESET => return error.ConnectionResetByPeer,
6733 .TIMEDOUT => return error.Timeout,
6734 .NOTCAPABLE => return error.AccessDenied,
6735 else => |err| return posix.unexpectedErrno(err),
6736 }
6737 },
6738 }
6739 }
6740 }
6741
6742 try current_thread.beginSyscall();
6743 while (true) {
6744 const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len));
6745 switch (posix.errno(rc)) {
6746 .SUCCESS => {
6747 current_thread.endSyscall();
6748 return @intCast(rc);
6749 },
6750 .INTR => {
6751 try current_thread.checkCancel();
6752 continue;
6753 },
6754 else => |e| {
6755 current_thread.endSyscall();
6756 switch (e) {
6757 .INVAL => |err| return errnoBug(err),
6758 .FAULT => |err| return errnoBug(err),
6759 .AGAIN => return error.WouldBlock,
6760 .BADF => |err| {
6761 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.
6762 return errnoBug(err); // File descriptor used after closed.
6763 },
6764 .IO => return error.InputOutput,
6765 .ISDIR => return error.IsDir,
6766 .NOBUFS => return error.SystemResources,
6767 .NOMEM => return error.SystemResources,
6768 .NOTCONN => return error.SocketUnconnected,
6769 .CONNRESET => return error.ConnectionResetByPeer,
6770 .TIMEDOUT => return error.Timeout,
6771 else => |err| return posix.unexpectedErrno(err),
6772 }
6773 },
6774 }
6775 }
6776}
6777
6778fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize {
6779 const t: *Threaded = @ptrCast(@alignCast(userdata));
6780 const current_thread = Thread.getCurrent(t);
6781
6782 const DWORD = windows.DWORD;
6783 var index: usize = 0;
6784 while (index < data.len and data[index].len == 0) index += 1;
6785 if (index == data.len) return 0;
6786 const buffer = data[index];
6787 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
6788
6789 while (true) {
6790 try current_thread.checkCancel();
6791 var n: DWORD = undefined;
6792 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0)
6793 return n;
6794 switch (windows.GetLastError()) {
6795 .IO_PENDING => |err| return windows.errorBug(err),
6796 .OPERATION_ABORTED => continue,
6797 .BROKEN_PIPE => return 0,
6798 .HANDLE_EOF => return 0,
6799 .NETNAME_DELETED => return error.ConnectionResetByPeer,
6800 .LOCK_VIOLATION => return error.LockViolation,
6801 .ACCESS_DENIED => return error.AccessDenied,
6802 .INVALID_HANDLE => return error.NotOpenForReading,
6803 else => |err| return windows.unexpectedError(err),
6804 }
6805 }
6806}
6807
6808fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
6809 const t: *Threaded = @ptrCast(@alignCast(userdata));
6810 const current_thread = Thread.getCurrent(t);
6811
6812 if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)");
6813
6814 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
6815 var i: usize = 0;
6816 for (data) |buf| {
6817 if (iovecs_buffer.len - i == 0) break;
6818 if (buf.len != 0) {
6819 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
6820 i += 1;
6821 }
6822 }
6823 if (i == 0) return 0;
6824 const dest = iovecs_buffer[0..i];
6825 assert(dest[0].len > 0);
6826
6827 if (native_os == .wasi and !builtin.link_libc) {
6828 try current_thread.beginSyscall();
6829 while (true) {
6830 var nread: usize = undefined;
6831 switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) {
6832 .SUCCESS => {
6833 current_thread.endSyscall();
6834 return nread;
6835 },
6836 .INTR => {
6837 try current_thread.checkCancel();
6838 continue;
6839 },
6840 else => |e| {
6841 current_thread.endSyscall();
6842 switch (e) {
6843 .INVAL => |err| return errnoBug(err),
6844 .FAULT => |err| return errnoBug(err),
6845 .AGAIN => |err| return errnoBug(err),
6846 .BADF => return error.NotOpenForReading, // File operation on directory.
6847 .IO => return error.InputOutput,
6848 .ISDIR => return error.IsDir,
6849 .NOBUFS => return error.SystemResources,
6850 .NOMEM => return error.SystemResources,
6851 .NOTCONN => return error.SocketUnconnected,
6852 .CONNRESET => return error.ConnectionResetByPeer,
6853 .TIMEDOUT => return error.Timeout,
6854 .NXIO => return error.Unseekable,
6855 .SPIPE => return error.Unseekable,
6856 .OVERFLOW => return error.Unseekable,
6857 .NOTCAPABLE => return error.AccessDenied,
6858 else => |err| return posix.unexpectedErrno(err),
6859 }
6860 },
6861 }
6862 }
6863 }
6864
6865 try current_thread.beginSyscall();
6866 while (true) {
6867 const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset));
6868 switch (posix.errno(rc)) {
6869 .SUCCESS => {
6870 current_thread.endSyscall();
6871 return @bitCast(rc);
6872 },
6873 .INTR => {
6874 try current_thread.checkCancel();
6875 continue;
6876 },
6877 else => |e| {
6878 current_thread.endSyscall();
6879 switch (e) {
6880 .INVAL => |err| return errnoBug(err),
6881 .FAULT => |err| return errnoBug(err),
6882 .AGAIN => return error.WouldBlock,
6883 .BADF => |err| {
6884 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.
6885 return errnoBug(err); // File descriptor used after closed.
6886 },
6887 .IO => return error.InputOutput,
6888 .ISDIR => return error.IsDir,
6889 .NOBUFS => return error.SystemResources,
6890 .NOMEM => return error.SystemResources,
6891 .NOTCONN => return error.SocketUnconnected,
6892 .CONNRESET => return error.ConnectionResetByPeer,
6893 .TIMEDOUT => return error.Timeout,
6894 .NXIO => return error.Unseekable,
6895 .SPIPE => return error.Unseekable,
6896 .OVERFLOW => return error.Unseekable,
6897 else => |err| return posix.unexpectedErrno(err),
6898 }
6899 },
6900 }
6901 }
6902}
6903
6904const fileReadPositional = switch (native_os) {
6905 .windows => fileReadPositionalWindows,
6906 else => fileReadPositionalPosix,
6907};
6908
6909fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
6910 const t: *Threaded = @ptrCast(@alignCast(userdata));
6911 const current_thread = Thread.getCurrent(t);
6912
6913 const DWORD = windows.DWORD;
6914
6915 var index: usize = 0;
6916 while (index < data.len and data[index].len == 0) index += 1;
6917 if (index == data.len) return 0;
6918 const buffer = data[index];
6919 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
6920
6921 var overlapped: windows.OVERLAPPED = .{
6922 .Internal = 0,
6923 .InternalHigh = 0,
6924 .DUMMYUNIONNAME = .{
6925 .DUMMYSTRUCTNAME = .{
6926 .Offset = @truncate(offset),
6927 .OffsetHigh = @truncate(offset >> 32),
6928 },
6929 },
6930 .hEvent = null,
6931 };
6932
6933 while (true) {
6934 try current_thread.checkCancel();
6935 var n: DWORD = undefined;
6936 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0)
6937 return n;
6938 switch (windows.GetLastError()) {
6939 .IO_PENDING => |err| return windows.errorBug(err),
6940 .OPERATION_ABORTED => continue,
6941 .BROKEN_PIPE => return 0,
6942 .HANDLE_EOF => return 0,
6943 .NETNAME_DELETED => return error.ConnectionResetByPeer,
6944 .LOCK_VIOLATION => return error.LockViolation,
6945 .ACCESS_DENIED => return error.AccessDenied,
6946 .INVALID_HANDLE => return error.NotOpenForReading,
6947 else => |err| return windows.unexpectedError(err),
6948 }
6949 }
6950}
6951
6952fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void {
6953 const t: *Threaded = @ptrCast(@alignCast(userdata));
6954 const current_thread = Thread.getCurrent(t);
6955 const fd = file.handle;
6956
6957 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
6958 var result: u64 = undefined;
6959 try current_thread.beginSyscall();
6960 while (true) {
6961 switch (posix.errno(posix.system.llseek(fd, @bitCast(offset), &result, posix.SEEK.CUR))) {
6962 .SUCCESS => {
6963 current_thread.endSyscall();
6964 return;
6965 },
6966 .INTR => {
6967 try current_thread.checkCancel();
6968 continue;
6969 },
6970 else => |e| {
6971 current_thread.endSyscall();
6972 switch (e) {
6973 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
6974 .INVAL => return error.Unseekable,
6975 .OVERFLOW => return error.Unseekable,
6976 .SPIPE => return error.Unseekable,
6977 .NXIO => return error.Unseekable,
6978 else => |err| return posix.unexpectedErrno(err),
6979 }
6980 },
6981 }
6982 }
6983 }
6984
6985 if (native_os == .windows) {
6986 try current_thread.checkCancel();
6987 return windows.SetFilePointerEx_CURRENT(fd, offset);
6988 }
6989
6990 if (native_os == .wasi and !builtin.link_libc) {
6991 var new_offset: std.os.wasi.filesize_t = undefined;
6992 try current_thread.beginSyscall();
6993 while (true) {
6994 switch (std.os.wasi.fd_seek(fd, offset, .CUR, &new_offset)) {
6995 .SUCCESS => {
6996 current_thread.endSyscall();
6997 return;
6998 },
6999 .INTR => {
7000 try current_thread.checkCancel();
7001 continue;
7002 },
7003 else => |e| {
7004 current_thread.endSyscall();
7005 switch (e) {
7006 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
7007 .INVAL => return error.Unseekable,
7008 .OVERFLOW => return error.Unseekable,
7009 .SPIPE => return error.Unseekable,
7010 .NXIO => return error.Unseekable,
7011 .NOTCAPABLE => return error.AccessDenied,
7012 else => |err| return posix.unexpectedErrno(err),
7013 }
7014 },
7015 }
7016 }
7017 }
7018
7019 if (posix.SEEK == void) return error.Unseekable;
7020
7021 try current_thread.beginSyscall();
7022 while (true) {
7023 switch (posix.errno(lseek_sym(fd, offset, posix.SEEK.CUR))) {
7024 .SUCCESS => {
7025 current_thread.endSyscall();
7026 return;
7027 },
7028 .INTR => {
7029 try current_thread.checkCancel();
7030 continue;
7031 },
7032 else => |e| {
7033 current_thread.endSyscall();
7034 switch (e) {
7035 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
7036 .INVAL => return error.Unseekable,
7037 .OVERFLOW => return error.Unseekable,
7038 .SPIPE => return error.Unseekable,
7039 .NXIO => return error.Unseekable,
7040 else => |err| return posix.unexpectedErrno(err),
7041 }
7042 },
7043 }
7044 }
7045}
7046
7047fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void {
7048 const t: *Threaded = @ptrCast(@alignCast(userdata));
7049 const current_thread = Thread.getCurrent(t);
7050 const fd = file.handle;
7051
7052 if (native_os == .windows) {
7053 try current_thread.checkCancel();
7054 return windows.SetFilePointerEx_BEGIN(fd, offset);
7055 }
7056
7057 if (native_os == .wasi and !builtin.link_libc) {
7058 try current_thread.beginSyscall();
7059 while (true) {
7060 var new_offset: std.os.wasi.filesize_t = undefined;
7061 switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
7062 .SUCCESS => {
7063 current_thread.endSyscall();
7064 return;
7065 },
7066 .INTR => {
7067 try current_thread.checkCancel();
7068 continue;
7069 },
7070 else => |e| {
7071 current_thread.endSyscall();
7072 switch (e) {
7073 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
7074 .INVAL => return error.Unseekable,
7075 .OVERFLOW => return error.Unseekable,
7076 .SPIPE => return error.Unseekable,
7077 .NXIO => return error.Unseekable,
7078 .NOTCAPABLE => return error.AccessDenied,
7079 else => |err| return posix.unexpectedErrno(err),
7080 }
7081 },
7082 }
7083 }
7084 }
7085
7086 if (posix.SEEK == void) return error.Unseekable;
7087
7088 return posixSeekTo(current_thread, fd, offset);
7089}
7090
7091fn posixSeekTo(current_thread: *Thread, fd: posix.fd_t, offset: u64) File.SeekError!void {
7092 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
7093 try current_thread.beginSyscall();
7094 while (true) {
7095 var result: u64 = undefined;
7096 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {
7097 .SUCCESS => {
7098 current_thread.endSyscall();
7099 return;
7100 },
7101 .INTR => {
7102 try current_thread.checkCancel();
7103 continue;
7104 },
7105 else => |e| {
7106 current_thread.endSyscall();
7107 switch (e) {
7108 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
7109 .INVAL => return error.Unseekable,
7110 .OVERFLOW => return error.Unseekable,
7111 .SPIPE => return error.Unseekable,
7112 .NXIO => return error.Unseekable,
7113 else => |err| return posix.unexpectedErrno(err),
7114 }
7115 },
7116 }
7117 }
7118 }
7119
7120 try current_thread.beginSyscall();
7121 while (true) {
7122 switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) {
7123 .SUCCESS => {
7124 current_thread.endSyscall();
7125 return;
7126 },
7127 .INTR => {
7128 try current_thread.checkCancel();
7129 continue;
7130 },
7131 else => |e| {
7132 current_thread.endSyscall();
7133 switch (e) {
7134 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
7135 .INVAL => return error.Unseekable,
7136 .OVERFLOW => return error.Unseekable,
7137 .SPIPE => return error.Unseekable,
7138 .NXIO => return error.Unseekable,
7139 else => |err| return posix.unexpectedErrno(err),
7140 }
7141 },
7142 }
7143 }
7144}
7145
7146fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.process.OpenExecutableError!File {
7147 const t: *Threaded = @ptrCast(@alignCast(userdata));
7148 switch (native_os) {
7149 .wasi => return error.OperationUnsupported,
7150 .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags),
7151 .windows => {
7152 // If ImagePathName is a symlink, then it will contain the path of the symlink,
7153 // not the path that the symlink points to. However, because we are opening
7154 // the file, we can let the openFileW call follow the symlink for us.
7155 const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
7156 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
7157 const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name);
7158 return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags);
7159 },
7160 .driverkit,
7161 .ios,
7162 .maccatalyst,
7163 .macos,
7164 .tvos,
7165 .visionos,
7166 .watchos,
7167 => {
7168 // _NSGetExecutablePath() returns a path that might be a symlink to
7169 // the executable. Here it does not matter since we open it.
7170 var symlink_path_buf: [posix.PATH_MAX + 1]u8 = undefined;
7171 var n: u32 = symlink_path_buf.len;
7172 const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &n);
7173 if (rc != 0) return error.NameTooLong;
7174 const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0);
7175 return dirOpenFilePosix(t, .cwd(), symlink_path, flags);
7176 },
7177 else => {
7178 var buffer: [Dir.max_path_bytes]u8 = undefined;
7179 const n = try processExecutablePath(t, &buffer);
7180 buffer[n] = 0;
7181 const executable_path = buffer[0..n :0];
7182 return dirOpenFilePosix(t, .cwd(), executable_path, flags);
7183 },
7184 }
7185}
7186
7187fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.ExecutablePathError!usize {
7188 const t: *Threaded = @ptrCast(@alignCast(userdata));
7189
7190 switch (native_os) {
7191 .driverkit,
7192 .ios,
7193 .maccatalyst,
7194 .macos,
7195 .tvos,
7196 .visionos,
7197 .watchos,
7198 => {
7199 // _NSGetExecutablePath() returns a path that might be a symlink to
7200 // the executable.
7201 var symlink_path_buf: [posix.PATH_MAX + 1]u8 = undefined;
7202 var n: u32 = symlink_path_buf.len;
7203 const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &n);
7204 if (rc != 0) return error.NameTooLong;
7205 const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0);
7206 return Io.Dir.realPathFileAbsolute(ioBasic(t), symlink_path, out_buffer) catch |err| switch (err) {
7207 error.NetworkNotFound => unreachable, // Windows-only
7208 else => |e| return e,
7209 };
7210 },
7211 .linux, .serenity => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/exe", out_buffer) catch |err| switch (err) {
7212 error.UnsupportedReparsePointType => unreachable, // Windows-only
7213 error.NetworkNotFound => unreachable, // Windows-only
7214 else => |e| return e,
7215 },
7216 .illumos => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/path/a.out", out_buffer) catch |err| switch (err) {
7217 error.UnsupportedReparsePointType => unreachable, // Windows-only
7218 error.NetworkNotFound => unreachable, // Windows-only
7219 else => |e| return e,
7220 },
7221 .freebsd, .dragonfly => {
7222 const current_thread = Thread.getCurrent(t);
7223 var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 };
7224 var out_len: usize = out_buffer.len;
7225 try current_thread.beginSyscall();
7226 while (true) {
7227 switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) {
7228 .SUCCESS => {
7229 current_thread.endSyscall();
7230 return out_len - 1; // discard terminating NUL
7231 },
7232 .INTR => {
7233 try current_thread.checkCancel();
7234 continue;
7235 },
7236 else => |e| {
7237 current_thread.endSyscall();
7238 switch (e) {
7239 .FAULT => |err| return errnoBug(err),
7240 .PERM => return error.PermissionDenied,
7241 .NOMEM => return error.SystemResources,
7242 .NOENT => |err| return errnoBug(err),
7243 else => |err| return posix.unexpectedErrno(err),
7244 }
7245 },
7246 }
7247 }
7248 },
7249 .netbsd => {
7250 const current_thread = Thread.getCurrent(t);
7251 var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME };
7252 var out_len: usize = out_buffer.len;
7253 try current_thread.beginSyscall();
7254 while (true) {
7255 switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) {
7256 .SUCCESS => {
7257 current_thread.endSyscall();
7258 return out_len - 1; // discard terminating NUL
7259 },
7260 .INTR => {
7261 try current_thread.checkCancel();
7262 continue;
7263 },
7264 else => |e| {
7265 current_thread.endSyscall();
7266 switch (e) {
7267 .FAULT => |err| return errnoBug(err),
7268 .PERM => return error.PermissionDenied,
7269 .NOMEM => return error.SystemResources,
7270 .NOENT => |err| return errnoBug(err),
7271 else => |err| return posix.unexpectedErrno(err),
7272 }
7273 },
7274 }
7275 }
7276 },
7277 .openbsd, .haiku => {
7278 // The best we can do on these operating systems is check based on
7279 // the first process argument.
7280 const argv0 = t.argv0.value orelse return error.OperationUnsupported;
7281 if (std.mem.findScalar(u8, argv0, '/') != null) {
7282 // argv[0] is a path (relative or absolute): use realpath(3) directly
7283 const current_thread = Thread.getCurrent(t);
7284 var resolved_buf: [std.c.PATH_MAX]u8 = undefined;
7285 try current_thread.beginSyscall();
7286 while (true) {
7287 if (std.c.realpath(argv0, &resolved_buf)) |p| {
7288 assert(p == &resolved_buf);
7289 break current_thread.endSyscall();
7290 } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) {
7291 .INTR => {
7292 try current_thread.checkCancel();
7293 continue;
7294 },
7295 else => |e| {
7296 current_thread.endSyscall();
7297 switch (e) {
7298 .ACCES => return error.AccessDenied,
7299 .INVAL => |err| return errnoBug(err), // the pathname argument is a null pointer
7300 .IO => return error.InputOutput,
7301 .LOOP => return error.SymLinkLoop,
7302 .NAMETOOLONG => return error.NameTooLong,
7303 .NOENT => return error.FileNotFound,
7304 .NOTDIR => return error.NotDir,
7305 .NOMEM => |err| return errnoBug(err), // sufficient storage space is unavailable for allocation
7306 else => |err| return posix.unexpectedErrno(err),
7307 }
7308 },
7309 }
7310 }
7311 const resolved = std.mem.sliceTo(&resolved_buf, 0);
7312 if (resolved.len > out_buffer.len)
7313 return error.NameTooLong;
7314 @memcpy(out_buffer[0..resolved.len], resolved);
7315 return resolved.len;
7316 } else if (argv0.len != 0) {
7317 // argv[0] is not empty (and not a path): search PATH
7318 t.scanEnviron();
7319 const PATH = t.environ.string.PATH orelse return error.FileNotFound;
7320 const current_thread = Thread.getCurrent(t);
7321 var it = std.mem.tokenizeScalar(u8, PATH, ':');
7322 it: while (it.next()) |dir| {
7323 var resolved_path_buf: [std.c.PATH_MAX]u8 = undefined;
7324 const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{
7325 dir, argv0,
7326 }, 0) catch continue;
7327
7328 var resolved_buf: [std.c.PATH_MAX]u8 = undefined;
7329 try current_thread.beginSyscall();
7330 while (true) {
7331 if (std.c.realpath(resolved_path, &resolved_buf)) |p| {
7332 assert(p == &resolved_buf);
7333 break current_thread.endSyscall();
7334 } else switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) {
7335 .INTR => {
7336 try current_thread.checkCancel();
7337 continue;
7338 },
7339 .NAMETOOLONG => {
7340 current_thread.endSyscall();
7341 return error.NameTooLong;
7342 },
7343 .NOMEM => {
7344 current_thread.endSyscall();
7345 return error.SystemResources;
7346 },
7347 .IO => {
7348 current_thread.endSyscall();
7349 return error.InputOutput;
7350 },
7351 .ACCES, .LOOP, .NOENT, .NOTDIR => {
7352 current_thread.endSyscall();
7353 continue :it;
7354 },
7355 else => |err| {
7356 current_thread.endSyscall();
7357 return posix.unexpectedErrno(err);
7358 },
7359 }
7360 }
7361 const resolved = std.mem.sliceTo(&resolved_buf, 0);
7362 if (resolved.len > out_buffer.len)
7363 return error.NameTooLong;
7364 @memcpy(out_buffer[0..resolved.len], resolved);
7365 return resolved.len;
7366 }
7367 }
7368 return error.FileNotFound;
7369 },
7370 .windows => {
7371 const current_thread = Thread.getCurrent(t);
7372 try current_thread.checkCancel();
7373 const w = windows;
7374 const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName;
7375 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
7376
7377 // If ImagePathName is a symlink, then it will contain the path of the
7378 // symlink, not the path that the symlink points to. We want the path
7379 // that the symlink points to, though, so we need to get the realpath.
7380 var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name);
7381
7382 const h_file = blk: {
7383 const res = w.OpenFile(path_name_w_buf.span(), .{
7384 .dir = null,
7385 .access_mask = .{
7386 .GENERIC = .{ .READ = true },
7387 .STANDARD = .{ .SYNCHRONIZE = true },
7388 },
7389 .creation = .OPEN,
7390 .filter = .any,
7391 }) catch |err| switch (err) {
7392 error.WouldBlock => unreachable,
7393 else => |e| return e,
7394 };
7395 break :blk res;
7396 };
7397 defer w.CloseHandle(h_file);
7398
7399 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
7400 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);
7401
7402 const len = std.unicode.calcWtf8Len(wide_slice);
7403 if (len > out_buffer.len)
7404 return error.NameTooLong;
7405
7406 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
7407 return end_index;
7408 },
7409 else => return error.OperationUnsupported,
7410 }
7411}
7412
7413fn fileWritePositional(
7414 userdata: ?*anyopaque,
7415 file: File,
7416 header: []const u8,
7417 data: []const []const u8,
7418 splat: usize,
7419 offset: u64,
7420) File.WritePositionalError!usize {
7421 const t: *Threaded = @ptrCast(@alignCast(userdata));
7422 const current_thread = Thread.getCurrent(t);
7423
7424 if (is_windows) {
7425 if (header.len != 0) {
7426 return writeFilePositionalWindows(current_thread, file.handle, header, offset);
7427 }
7428 for (data[0 .. data.len - 1]) |buf| {
7429 if (buf.len == 0) continue;
7430 return writeFilePositionalWindows(current_thread, file.handle, buf, offset);
7431 }
7432 const pattern = data[data.len - 1];
7433 if (pattern.len == 0 or splat == 0) return 0;
7434 return writeFilePositionalWindows(current_thread, file.handle, pattern, offset);
7435 }
7436
7437 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
7438 var iovlen: iovlen_t = 0;
7439 addBuf(&iovecs, &iovlen, header);
7440 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes);
7441 const pattern = data[data.len - 1];
7442 if (iovecs.len - iovlen != 0) switch (splat) {
7443 0 => {},
7444 1 => addBuf(&iovecs, &iovlen, pattern),
7445 else => switch (pattern.len) {
7446 0 => {},
7447 1 => {
7448 var backup_buffer: [splat_buffer_size]u8 = undefined;
7449 const splat_buffer = &backup_buffer;
7450 const memset_len = @min(splat_buffer.len, splat);
7451 const buf = splat_buffer[0..memset_len];
7452 @memset(buf, pattern[0]);
7453 addBuf(&iovecs, &iovlen, buf);
7454 var remaining_splat = splat - buf.len;
7455 while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) {
7456 assert(buf.len == splat_buffer.len);
7457 addBuf(&iovecs, &iovlen, splat_buffer);
7458 remaining_splat -= splat_buffer.len;
7459 }
7460 addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]);
7461 },
7462 else => for (0..@min(splat, iovecs.len - iovlen)) |_| {
7463 addBuf(&iovecs, &iovlen, pattern);
7464 },
7465 },
7466 };
31637467
3164const fileReadStreaming = switch (native_os) {
3165 .windows => fileReadStreamingWindows,
3166 else => fileReadStreamingPosix,
3167};
7468 if (iovlen == 0) return 0;
7469
7470 if (native_os == .wasi and !builtin.link_libc) {
7471 var n_written: usize = undefined;
7472 try current_thread.beginSyscall();
7473 while (true) {
7474 switch (std.os.wasi.fd_pwrite(file.handle, &iovecs, iovlen, offset, &n_written)) {
7475 .SUCCESS => {
7476 current_thread.endSyscall();
7477 return n_written;
7478 },
7479 .INTR => {
7480 try current_thread.checkCancel();
7481 continue;
7482 },
7483 else => |e| {
7484 current_thread.endSyscall();
7485 switch (e) {
7486 .INVAL => |err| return errnoBug(err),
7487 .FAULT => |err| return errnoBug(err),
7488 .AGAIN => |err| return errnoBug(err),
7489 .BADF => return error.NotOpenForWriting, // can be a race condition.
7490 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
7491 .DQUOT => return error.DiskQuota,
7492 .FBIG => return error.FileTooBig,
7493 .IO => return error.InputOutput,
7494 .NOSPC => return error.NoSpaceLeft,
7495 .PERM => return error.PermissionDenied,
7496 .PIPE => return error.BrokenPipe,
7497 .NOTCAPABLE => return error.AccessDenied,
7498 .NXIO => return error.Unseekable,
7499 .SPIPE => return error.Unseekable,
7500 .OVERFLOW => return error.Unseekable,
7501 else => |err| return posix.unexpectedErrno(err),
7502 }
7503 },
7504 }
7505 }
7506 }
7507
7508 try current_thread.beginSyscall();
7509 while (true) {
7510 const rc = pwritev_sym(file.handle, &iovecs, @intCast(iovlen), @bitCast(offset));
7511 switch (posix.errno(rc)) {
7512 .SUCCESS => {
7513 current_thread.endSyscall();
7514 return @intCast(rc);
7515 },
7516 .INTR => {
7517 try current_thread.checkCancel();
7518 continue;
7519 },
7520 else => |e| {
7521 current_thread.endSyscall();
7522 switch (e) {
7523 .INVAL => |err| return errnoBug(err),
7524 .FAULT => |err| return errnoBug(err),
7525 .AGAIN => return error.WouldBlock,
7526 .BADF => return error.NotOpenForWriting, // Usually a race condition.
7527 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
7528 .DQUOT => return error.DiskQuota,
7529 .FBIG => return error.FileTooBig,
7530 .IO => return error.InputOutput,
7531 .NOSPC => return error.NoSpaceLeft,
7532 .PERM => return error.PermissionDenied,
7533 .PIPE => return error.BrokenPipe,
7534 .CONNRESET => |err| return errnoBug(err), // Not a socket handle.
7535 .BUSY => return error.DeviceBusy,
7536 .TXTBSY => return error.FileBusy,
7537 .NXIO => return error.Unseekable,
7538 .SPIPE => return error.Unseekable,
7539 .OVERFLOW => return error.Unseekable,
7540 else => |err| return posix.unexpectedErrno(err),
7541 }
7542 },
7543 }
7544 }
7545}
7546
7547fn writeFilePositionalWindows(
7548 current_thread: *Thread,
7549 handle: windows.HANDLE,
7550 bytes: []const u8,
7551 offset: u64,
7552) File.WritePositionalError!usize {
7553 try current_thread.checkCancel();
7554
7555 var bytes_written: windows.DWORD = undefined;
7556 var overlapped: windows.OVERLAPPED = .{
7557 .Internal = 0,
7558 .InternalHigh = 0,
7559 .DUMMYUNIONNAME = .{
7560 .DUMMYSTRUCTNAME = .{
7561 .Offset = @truncate(offset),
7562 .OffsetHigh = @truncate(offset >> 32),
7563 },
7564 },
7565 .hEvent = null,
7566 };
7567 const adjusted_len = std.math.lossyCast(u32, bytes.len);
7568 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) == 0) {
7569 switch (windows.GetLastError()) {
7570 .INVALID_USER_BUFFER => return error.SystemResources,
7571 .NOT_ENOUGH_MEMORY => return error.SystemResources,
7572 .OPERATION_ABORTED => return error.Canceled,
7573 .NOT_ENOUGH_QUOTA => return error.SystemResources,
7574 .NO_DATA => return error.BrokenPipe,
7575 .INVALID_HANDLE => return error.NotOpenForWriting,
7576 .LOCK_VIOLATION => return error.LockViolation,
7577 .ACCESS_DENIED => return error.AccessDenied,
7578 .WORKING_SET_QUOTA => return error.SystemResources,
7579 else => |err| return windows.unexpectedError(err),
7580 }
7581 }
7582 return bytes_written;
7583}
31687584
3169fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize {
7585fn fileWriteStreaming(
7586 userdata: ?*anyopaque,
7587 file: File,
7588 header: []const u8,
7589 data: []const []const u8,
7590 splat: usize,
7591) File.Writer.Error!usize {
31707592 const t: *Threaded = @ptrCast(@alignCast(userdata));
31717593 const current_thread = Thread.getCurrent(t);
31727594
3173 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
3174 var i: usize = 0;
3175 for (data) |buf| {
3176 if (iovecs_buffer.len - i == 0) break;
3177 if (buf.len != 0) {
3178 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
3179 i += 1;
7595 if (is_windows) {
7596 if (header.len != 0) {
7597 return writeFileStreamingWindows(current_thread, file.handle, header);
7598 }
7599 for (data[0 .. data.len - 1]) |buf| {
7600 if (buf.len == 0) continue;
7601 return writeFileStreamingWindows(current_thread, file.handle, buf);
31807602 }
7603 const pattern = data[data.len - 1];
7604 if (pattern.len == 0 or splat == 0) return 0;
7605 return writeFileStreamingWindows(current_thread, file.handle, pattern);
31817606 }
3182 const dest = iovecs_buffer[0..i];
3183 assert(dest[0].len > 0);
7607
7608 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
7609 var iovlen: iovlen_t = 0;
7610 addBuf(&iovecs, &iovlen, header);
7611 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &iovlen, bytes);
7612 const pattern = data[data.len - 1];
7613 if (iovecs.len - iovlen != 0) switch (splat) {
7614 0 => {},
7615 1 => addBuf(&iovecs, &iovlen, pattern),
7616 else => switch (pattern.len) {
7617 0 => {},
7618 1 => {
7619 var backup_buffer: [splat_buffer_size]u8 = undefined;
7620 const splat_buffer = &backup_buffer;
7621 const memset_len = @min(splat_buffer.len, splat);
7622 const buf = splat_buffer[0..memset_len];
7623 @memset(buf, pattern[0]);
7624 addBuf(&iovecs, &iovlen, buf);
7625 var remaining_splat = splat - buf.len;
7626 while (remaining_splat > splat_buffer.len and iovecs.len - iovlen != 0) {
7627 assert(buf.len == splat_buffer.len);
7628 addBuf(&iovecs, &iovlen, splat_buffer);
7629 remaining_splat -= splat_buffer.len;
7630 }
7631 addBuf(&iovecs, &iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]);
7632 },
7633 else => for (0..@min(splat, iovecs.len - iovlen)) |_| {
7634 addBuf(&iovecs, &iovlen, pattern);
7635 },
7636 },
7637 };
7638
7639 if (iovlen == 0) return 0;
31847640
31857641 if (native_os == .wasi and !builtin.link_libc) {
7642 var n_written: usize = undefined;
31867643 try current_thread.beginSyscall();
31877644 while (true) {
3188 var nread: usize = undefined;
3189 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
7645 switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) {
31907646 .SUCCESS => {
31917647 current_thread.endSyscall();
3192 return nread;
7648 return n_written;
31937649 },
31947650 .INTR => {
31957651 try current_thread.checkCancel();
31967652 continue;
31977653 },
3198 .CANCELED => return current_thread.endSyscallCanceled(),
31997654 else => |e| {
32007655 current_thread.endSyscall();
32017656 switch (e) {
32027657 .INVAL => |err| return errnoBug(err),
32037658 .FAULT => |err| return errnoBug(err),
3204 .BADF => return error.NotOpenForReading, // File operation on directory.
7659 .AGAIN => |err| return errnoBug(err),
7660 .BADF => return error.NotOpenForWriting, // can be a race condition.
7661 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
7662 .DQUOT => return error.DiskQuota,
7663 .FBIG => return error.FileTooBig,
32057664 .IO => return error.InputOutput,
3206 .ISDIR => return error.IsDir,
3207 .NOBUFS => return error.SystemResources,
3208 .NOMEM => return error.SystemResources,
3209 .NOTCONN => return error.SocketUnconnected,
3210 .CONNRESET => return error.ConnectionResetByPeer,
3211 .TIMEDOUT => return error.Timeout,
7665 .NOSPC => return error.NoSpaceLeft,
7666 .PERM => return error.PermissionDenied,
7667 .PIPE => return error.BrokenPipe,
32127668 .NOTCAPABLE => return error.AccessDenied,
32137669 else => |err| return posix.unexpectedErrno(err),
32147670 }
......@@ -3219,7 +7675,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io
32197675
32207676 try current_thread.beginSyscall();
32217677 while (true) {
3222 const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len));
7678 const rc = posix.system.writev(file.handle, &iovecs, @intCast(iovlen));
32237679 switch (posix.errno(rc)) {
32247680 .SUCCESS => {
32257681 current_thread.endSyscall();
......@@ -3229,25 +7685,22 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io
32297685 try current_thread.checkCancel();
32307686 continue;
32317687 },
3232 .CANCELED => return current_thread.endSyscallCanceled(),
32337688 else => |e| {
32347689 current_thread.endSyscall();
32357690 switch (e) {
32367691 .INVAL => |err| return errnoBug(err),
32377692 .FAULT => |err| return errnoBug(err),
3238 .SRCH => return error.ProcessNotFound,
32397693 .AGAIN => return error.WouldBlock,
3240 .BADF => |err| {
3241 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.
3242 return errnoBug(err); // File descriptor used after closed.
3243 },
7694 .BADF => return error.NotOpenForWriting, // Can be a race condition.
7695 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
7696 .DQUOT => return error.DiskQuota,
7697 .FBIG => return error.FileTooBig,
32447698 .IO => return error.InputOutput,
3245 .ISDIR => return error.IsDir,
3246 .NOBUFS => return error.SystemResources,
3247 .NOMEM => return error.SystemResources,
3248 .NOTCONN => return error.SocketUnconnected,
3249 .CONNRESET => return error.ConnectionResetByPeer,
3250 .TIMEDOUT => return error.Timeout,
7699 .NOSPC => return error.NoSpaceLeft,
7700 .PERM => return error.PermissionDenied,
7701 .PIPE => return error.BrokenPipe,
7702 .CONNRESET => |err| return errnoBug(err), // Not a socket handle.
7703 .BUSY => return error.DeviceBusy,
32517704 else => |err| return posix.unexpectedErrno(err),
32527705 }
32537706 },
......@@ -3255,325 +7708,653 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io
32557708 }
32567709}
32577710
3258fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize {
3259 const t: *Threaded = @ptrCast(@alignCast(userdata));
3260 const current_thread = Thread.getCurrent(t);
3261
3262 const DWORD = windows.DWORD;
3263 var index: usize = 0;
3264 while (data[index].len == 0) index += 1;
3265 const buffer = data[index];
3266 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
7711fn writeFileStreamingWindows(
7712 current_thread: *Thread,
7713 handle: windows.HANDLE,
7714 bytes: []const u8,
7715) File.Writer.Error!usize {
7716 try current_thread.checkCancel();
32677717
3268 while (true) {
3269 try current_thread.checkCancel();
3270 var n: DWORD = undefined;
3271 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0)
3272 return n;
7718 var bytes_written: windows.DWORD = undefined;
7719 const adjusted_len = std.math.lossyCast(u32, bytes.len);
7720 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) == 0) {
32737721 switch (windows.GetLastError()) {
3274 .IO_PENDING => |err| return windows.errorBug(err),
3275 .OPERATION_ABORTED => continue,
3276 .BROKEN_PIPE => return 0,
3277 .HANDLE_EOF => return 0,
3278 .NETNAME_DELETED => return error.ConnectionResetByPeer,
7722 .INVALID_USER_BUFFER => return error.SystemResources,
7723 .NOT_ENOUGH_MEMORY => return error.SystemResources,
7724 .OPERATION_ABORTED => return error.Canceled,
7725 .NOT_ENOUGH_QUOTA => return error.SystemResources,
7726 .NO_DATA => return error.BrokenPipe,
7727 .INVALID_HANDLE => return error.NotOpenForWriting,
32797728 .LOCK_VIOLATION => return error.LockViolation,
32807729 .ACCESS_DENIED => return error.AccessDenied,
3281 .INVALID_HANDLE => return error.NotOpenForReading,
7730 .WORKING_SET_QUOTA => return error.SystemResources,
32827731 else => |err| return windows.unexpectedError(err),
32837732 }
32847733 }
7734 return bytes_written;
32857735}
32867736
3287fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
7737fn fileWriteFileStreaming(
7738 userdata: ?*anyopaque,
7739 file: File,
7740 header: []const u8,
7741 file_reader: *File.Reader,
7742 limit: Io.Limit,
7743) File.Writer.WriteFileError!usize {
32887744 const t: *Threaded = @ptrCast(@alignCast(userdata));
3289 const current_thread = Thread.getCurrent(t);
3290
3291 if (!have_preadv) @compileError("TODO");
3292
3293 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
3294 var i: usize = 0;
3295 for (data) |buf| {
3296 if (iovecs_buffer.len - i == 0) break;
3297 if (buf.len != 0) {
3298 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
3299 i += 1;
7745 const reader_buffered = file_reader.interface.buffered();
7746 if (reader_buffered.len >= @intFromEnum(limit)) {
7747 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
7748 file_reader.interface.toss(n -| header.len);
7749 return n;
7750 }
7751 const file_limit = @intFromEnum(limit) - reader_buffered.len;
7752 const out_fd = file.handle;
7753 const in_fd = file_reader.file.handle;
7754
7755 if (file_reader.size) |size| {
7756 if (size - file_reader.pos == 0) {
7757 if (reader_buffered.len != 0) {
7758 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
7759 file_reader.interface.toss(n -| header.len);
7760 return n;
7761 } else {
7762 return error.EndOfStream;
7763 }
33007764 }
33017765 }
3302 const dest = iovecs_buffer[0..i];
3303 assert(dest[0].len > 0);
33047766
3305 if (native_os == .wasi and !builtin.link_libc) {
7767 if (native_os == .freebsd) sf: {
7768 // Try using sendfile on FreeBSD.
7769 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf;
7770 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
7771 var hdtr_data: std.c.sf_hdtr = undefined;
7772 var headers: [2]posix.iovec_const = undefined;
7773 var headers_i: u8 = 0;
7774 if (header.len != 0) {
7775 headers[headers_i] = .{ .base = header.ptr, .len = header.len };
7776 headers_i += 1;
7777 }
7778 if (reader_buffered.len != 0) {
7779 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
7780 headers_i += 1;
7781 }
7782 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
7783 hdtr_data = .{
7784 .headers = &headers,
7785 .hdr_cnt = headers_i,
7786 .trailers = null,
7787 .trl_cnt = 0,
7788 };
7789 break :b &hdtr_data;
7790 };
7791 var sbytes: std.c.off_t = 0;
7792 const nbytes: usize = @min(file_limit, std.math.maxInt(usize));
7793 const flags = 0;
7794
7795 const current_thread = Thread.getCurrent(t);
7796 try current_thread.beginSyscall();
7797 while (true) {
7798 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) {
7799 .SUCCESS => {
7800 current_thread.endSyscall();
7801 break;
7802 },
7803 .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => {
7804 // Give calling code chance to observe before trying
7805 // something else.
7806 current_thread.endSyscall();
7807 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
7808 return 0;
7809 },
7810 .INTR, .BUSY => {
7811 if (sbytes == 0) {
7812 try current_thread.checkCancel();
7813 continue;
7814 } else {
7815 // Even if we are being canceled, there have been side
7816 // effects, so it is better to report those side
7817 // effects to the caller.
7818 current_thread.endSyscall();
7819 break;
7820 }
7821 },
7822 .AGAIN => {
7823 current_thread.endSyscall();
7824 if (sbytes == 0) return error.WouldBlock;
7825 break;
7826 },
7827 else => |e| {
7828 current_thread.endSyscall();
7829 assert(error.Unexpected == switch (e) {
7830 .NOTCONN => return error.BrokenPipe,
7831 .IO => return error.InputOutput,
7832 .PIPE => return error.BrokenPipe,
7833 .NOBUFS => return error.SystemResources,
7834 .BADF => |err| errnoBug(err),
7835 .FAULT => |err| errnoBug(err),
7836 else => |err| posix.unexpectedErrno(err),
7837 });
7838 // Give calling code chance to observe the error before trying
7839 // something else.
7840 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
7841 return 0;
7842 },
7843 }
7844 }
7845 if (sbytes == 0) {
7846 file_reader.size = file_reader.pos;
7847 return error.EndOfStream;
7848 }
7849 const ubytes: usize = @intCast(sbytes);
7850 file_reader.interface.toss(ubytes -| header.len);
7851 return ubytes;
7852 }
7853
7854 if (is_darwin) sf: {
7855 // Try using sendfile on macOS.
7856 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf;
7857 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
7858 var hdtr_data: std.c.sf_hdtr = undefined;
7859 var headers: [2]posix.iovec_const = undefined;
7860 var headers_i: u8 = 0;
7861 if (header.len != 0) {
7862 headers[headers_i] = .{ .base = header.ptr, .len = header.len };
7863 headers_i += 1;
7864 }
7865 if (reader_buffered.len != 0) {
7866 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
7867 headers_i += 1;
7868 }
7869 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
7870 hdtr_data = .{
7871 .headers = &headers,
7872 .hdr_cnt = headers_i,
7873 .trailers = null,
7874 .trl_cnt = 0,
7875 };
7876 break :b &hdtr_data;
7877 };
7878 const max_count = std.math.maxInt(i32); // Avoid EINVAL.
7879 var len: std.c.off_t = @min(file_limit, max_count);
7880 const flags = 0;
7881 const current_thread = Thread.getCurrent(t);
7882 try current_thread.beginSyscall();
7883 while (true) {
7884 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) {
7885 .SUCCESS => {
7886 current_thread.endSyscall();
7887 break;
7888 },
7889 .OPNOTSUPP, .NOTSOCK, .NOSYS => {
7890 // Give calling code chance to observe before trying
7891 // something else.
7892 current_thread.endSyscall();
7893 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
7894 return 0;
7895 },
7896 .INTR => {
7897 if (len == 0) {
7898 try current_thread.checkCancel();
7899 continue;
7900 } else {
7901 // Even if we are being canceled, there have been side
7902 // effects, so it is better to report those side
7903 // effects to the caller.
7904 current_thread.endSyscall();
7905 break;
7906 }
7907 },
7908 .AGAIN => {
7909 current_thread.endSyscall();
7910 if (len == 0) return error.WouldBlock;
7911 break;
7912 },
7913 else => |e| {
7914 current_thread.endSyscall();
7915 assert(error.Unexpected == switch (e) {
7916 .NOTCONN => return error.BrokenPipe,
7917 .IO => return error.InputOutput,
7918 .PIPE => return error.BrokenPipe,
7919 .BADF => |err| errnoBug(err),
7920 .FAULT => |err| errnoBug(err),
7921 .INVAL => |err| errnoBug(err),
7922 else => |err| posix.unexpectedErrno(err),
7923 });
7924 // Give calling code chance to observe the error before trying
7925 // something else.
7926 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
7927 return 0;
7928 },
7929 }
7930 }
7931 if (len == 0) {
7932 file_reader.size = file_reader.pos;
7933 return error.EndOfStream;
7934 }
7935 const u_len: usize = @bitCast(len);
7936 file_reader.interface.toss(u_len -| header.len);
7937 return u_len;
7938 }
7939
7940 if (native_os == .linux) sf: {
7941 // Try using sendfile on Linux.
7942 if (@atomicLoad(UseSendfile, &t.use_sendfile, .monotonic) == .disabled) break :sf;
7943 // Linux sendfile does not support headers.
7944 if (header.len != 0 or reader_buffered.len != 0) {
7945 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
7946 file_reader.interface.toss(n -| header.len);
7947 return n;
7948 }
7949 const max_count = 0x7ffff000; // Avoid EINVAL.
7950 var off: std.os.linux.off_t = undefined;
7951 const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) {
7952 .positional => o: {
7953 const size = file_reader.getSize() catch return 0;
7954 off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed;
7955 break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) };
7956 },
7957 .streaming => .{ null, limit.minInt(max_count) },
7958 .streaming_simple, .positional_simple => break :sf,
7959 .failure => return error.ReadFailed,
7960 };
7961 const current_thread = Thread.getCurrent(t);
33067962 try current_thread.beginSyscall();
3307 while (true) {
3308 var nread: usize = undefined;
3309 switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) {
7963 const n: usize = while (true) {
7964 const rc = sendfile_sym(out_fd, in_fd, off_ptr, count);
7965 switch (posix.errno(rc)) {
33107966 .SUCCESS => {
33117967 current_thread.endSyscall();
3312 return nread;
7968 break @intCast(rc);
7969 },
7970 .NOSYS, .INVAL => {
7971 // Give calling code chance to observe before trying
7972 // something else.
7973 current_thread.endSyscall();
7974 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
7975 return 0;
33137976 },
33147977 .INTR => {
33157978 try current_thread.checkCancel();
33167979 continue;
33177980 },
3318 .CANCELED => return current_thread.endSyscallCanceled(),
33197981 else => |e| {
33207982 current_thread.endSyscall();
3321 switch (e) {
3322 .INVAL => |err| return errnoBug(err),
3323 .FAULT => |err| return errnoBug(err),
3324 .AGAIN => |err| return errnoBug(err),
3325 .BADF => return error.NotOpenForReading, // File operation on directory.
7983 assert(error.Unexpected == switch (e) {
7984 .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
7985 .AGAIN => return error.WouldBlock,
33267986 .IO => return error.InputOutput,
3327 .ISDIR => return error.IsDir,
3328 .NOBUFS => return error.SystemResources,
7987 .PIPE => return error.BrokenPipe,
33297988 .NOMEM => return error.SystemResources,
3330 .NOTCONN => return error.SocketUnconnected,
3331 .CONNRESET => return error.ConnectionResetByPeer,
3332 .TIMEDOUT => return error.Timeout,
3333 .NXIO => return error.Unseekable,
3334 .SPIPE => return error.Unseekable,
3335 .OVERFLOW => return error.Unseekable,
3336 .NOTCAPABLE => return error.AccessDenied,
3337 else => |err| return posix.unexpectedErrno(err),
3338 }
7989 .NXIO, .SPIPE => {
7990 file_reader.mode = file_reader.mode.toStreaming();
7991 const pos = file_reader.pos;
7992 if (pos != 0) {
7993 file_reader.pos = 0;
7994 file_reader.seekBy(@intCast(pos)) catch {
7995 file_reader.mode = .failure;
7996 return error.ReadFailed;
7997 };
7998 }
7999 return 0;
8000 },
8001 .BADF => |err| errnoBug(err), // Always a race condition.
8002 .FAULT => |err| errnoBug(err), // Segmentation fault.
8003 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large of a `count`.
8004 else => |err| posix.unexpectedErrno(err),
8005 });
8006 // Give calling code chance to observe the error before trying
8007 // something else.
8008 @atomicStore(UseSendfile, &t.use_sendfile, .disabled, .monotonic);
8009 return 0;
33398010 },
33408011 }
8012 };
8013 if (n == 0) {
8014 file_reader.size = file_reader.pos;
8015 return error.EndOfStream;
33418016 }
8017 file_reader.pos += n;
8018 return n;
33428019 }
33438020
3344 try current_thread.beginSyscall();
3345 while (true) {
3346 const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset));
3347 switch (posix.errno(rc)) {
3348 .SUCCESS => {
3349 current_thread.endSyscall();
3350 return @bitCast(rc);
3351 },
3352 .INTR => {
3353 try current_thread.checkCancel();
3354 continue;
8021 if (have_copy_file_range) cfr: {
8022 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr;
8023 if (header.len != 0 or reader_buffered.len != 0) {
8024 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
8025 file_reader.interface.toss(n -| header.len);
8026 return n;
8027 }
8028 var off_in: i64 = undefined;
8029 const off_in_ptr: ?*i64 = switch (file_reader.mode) {
8030 .positional_simple, .streaming_simple => return error.Unimplemented,
8031 .positional => p: {
8032 off_in = @intCast(file_reader.pos);
8033 break :p &off_in;
8034 },
8035 .streaming => null,
8036 .failure => return error.ReadFailed,
8037 };
8038 const current_thread = Thread.getCurrent(t);
8039 const n: usize = switch (native_os) {
8040 .linux => n: {
8041 try current_thread.beginSyscall();
8042 while (true) {
8043 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0);
8044 switch (linux_copy_file_range_sys.errno(rc)) {
8045 .SUCCESS => {
8046 current_thread.endSyscall();
8047 break :n @intCast(rc);
8048 },
8049 .INTR => {
8050 try current_thread.checkCancel();
8051 continue;
8052 },
8053 .OPNOTSUPP, .INVAL, .NOSYS => {
8054 // Give calling code chance to observe before trying
8055 // something else.
8056 current_thread.endSyscall();
8057 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
8058 return 0;
8059 },
8060 else => |e| {
8061 current_thread.endSyscall();
8062 assert(error.Unexpected == switch (e) {
8063 .FBIG => return error.FileTooBig,
8064 .IO => return error.InputOutput,
8065 .NOMEM => return error.SystemResources,
8066 .NOSPC => return error.NoSpaceLeft,
8067 .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count.
8068 .PERM => return error.PermissionDenied,
8069 .BUSY => return error.DeviceBusy,
8070 .TXTBSY => return error.FileBusy,
8071 // copy_file_range can still work but not on
8072 // this pair of file descriptors.
8073 .XDEV => return error.Unimplemented,
8074 .ISDIR => |err| errnoBug(err),
8075 .BADF => |err| errnoBug(err),
8076 else => |err| posix.unexpectedErrno(err),
8077 });
8078 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
8079 return 0;
8080 },
8081 }
8082 }
33558083 },
3356 .CANCELED => return current_thread.endSyscallCanceled(),
3357 else => |e| {
3358 current_thread.endSyscall();
3359 switch (e) {
3360 .INVAL => |err| return errnoBug(err),
3361 .FAULT => |err| return errnoBug(err),
3362 .SRCH => return error.ProcessNotFound,
3363 .AGAIN => return error.WouldBlock,
3364 .BADF => |err| {
3365 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.
3366 return errnoBug(err); // File descriptor used after closed.
3367 },
3368 .IO => return error.InputOutput,
3369 .ISDIR => return error.IsDir,
3370 .NOBUFS => return error.SystemResources,
3371 .NOMEM => return error.SystemResources,
3372 .NOTCONN => return error.SocketUnconnected,
3373 .CONNRESET => return error.ConnectionResetByPeer,
3374 .TIMEDOUT => return error.Timeout,
3375 .NXIO => return error.Unseekable,
3376 .SPIPE => return error.Unseekable,
3377 .OVERFLOW => return error.Unseekable,
3378 else => |err| return posix.unexpectedErrno(err),
8084 .freebsd => n: {
8085 try current_thread.beginSyscall();
8086 while (true) {
8087 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0);
8088 switch (std.c.errno(rc)) {
8089 .SUCCESS => {
8090 current_thread.endSyscall();
8091 break :n @intCast(rc);
8092 },
8093 .INTR => {
8094 try current_thread.checkCancel();
8095 continue;
8096 },
8097 .OPNOTSUPP, .INVAL, .NOSYS => {
8098 // Give calling code chance to observe before trying
8099 // something else.
8100 current_thread.endSyscall();
8101 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
8102 return 0;
8103 },
8104 else => |e| {
8105 current_thread.endSyscall();
8106 assert(error.Unexpected == switch (e) {
8107 .FBIG => return error.FileTooBig,
8108 .IO => return error.InputOutput,
8109 .INTEGRITY => return error.CorruptedData,
8110 .NOSPC => return error.NoSpaceLeft,
8111 .ISDIR => |err| errnoBug(err),
8112 .BADF => |err| errnoBug(err),
8113 else => |err| posix.unexpectedErrno(err),
8114 });
8115 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
8116 return 0;
8117 },
8118 }
33798119 }
33808120 },
8121 else => comptime unreachable,
8122 };
8123 if (n == 0) {
8124 file_reader.size = file_reader.pos;
8125 return error.EndOfStream;
33818126 }
8127 file_reader.pos += n;
8128 return n;
33828129 }
3383}
33848130
3385const fileReadPositional = switch (native_os) {
3386 .windows => fileReadPositionalWindows,
3387 else => fileReadPositionalPosix,
3388};
8131 return error.Unimplemented;
8132}
33898133
3390fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
8134fn netWriteFile(
8135 userdata: ?*anyopaque,
8136 socket_handle: net.Socket.Handle,
8137 header: []const u8,
8138 file_reader: *File.Reader,
8139 limit: Io.Limit,
8140) net.Stream.Writer.WriteFileError!usize {
33918141 const t: *Threaded = @ptrCast(@alignCast(userdata));
3392 const current_thread = Thread.getCurrent(t);
3393
3394 const DWORD = windows.DWORD;
3395
3396 var index: usize = 0;
3397 while (data[index].len == 0) index += 1;
3398 const buffer = data[index];
3399 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
3400
3401 var overlapped: windows.OVERLAPPED = .{
3402 .Internal = 0,
3403 .InternalHigh = 0,
3404 .DUMMYUNIONNAME = .{
3405 .DUMMYSTRUCTNAME = .{
3406 .Offset = @truncate(offset),
3407 .OffsetHigh = @truncate(offset >> 32),
3408 },
3409 },
3410 .hEvent = null,
3411 };
3412
3413 while (true) {
3414 try current_thread.checkCancel();
3415 var n: DWORD = undefined;
3416 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0)
3417 return n;
3418 switch (windows.GetLastError()) {
3419 .IO_PENDING => |err| return windows.errorBug(err),
3420 .OPERATION_ABORTED => continue,
3421 .BROKEN_PIPE => return 0,
3422 .HANDLE_EOF => return 0,
3423 .NETNAME_DELETED => return error.ConnectionResetByPeer,
3424 .LOCK_VIOLATION => return error.LockViolation,
3425 .ACCESS_DENIED => return error.AccessDenied,
3426 .INVALID_HANDLE => return error.NotOpenForReading,
3427 else => |err| return windows.unexpectedError(err),
3428 }
3429 }
8142 _ = t;
8143 _ = socket_handle;
8144 _ = header;
8145 _ = file_reader;
8146 _ = limit;
8147 @panic("TODO implement netWriteFile");
34308148}
34318149
3432fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void {
8150fn netWriteFileUnavailable(
8151 userdata: ?*anyopaque,
8152 socket_handle: net.Socket.Handle,
8153 header: []const u8,
8154 file_reader: *File.Reader,
8155 limit: Io.Limit,
8156) net.Stream.Writer.WriteFileError!usize {
34338157 const t: *Threaded = @ptrCast(@alignCast(userdata));
34348158 _ = t;
3435 _ = file;
3436 _ = offset;
3437 @panic("TODO implement fileSeekBy");
8159 _ = socket_handle;
8160 _ = header;
8161 _ = file_reader;
8162 _ = limit;
8163 return error.NetworkDown;
34388164}
34398165
3440fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void {
8166fn fileWriteFilePositional(
8167 userdata: ?*anyopaque,
8168 file: File,
8169 header: []const u8,
8170 file_reader: *File.Reader,
8171 limit: Io.Limit,
8172 offset: u64,
8173) File.WriteFilePositionalError!usize {
34418174 const t: *Threaded = @ptrCast(@alignCast(userdata));
3442 const current_thread = Thread.getCurrent(t);
3443 const fd = file.handle;
3444
3445 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
3446 try current_thread.beginSyscall();
3447 while (true) {
3448 var result: u64 = undefined;
3449 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {
3450 .SUCCESS => {
3451 current_thread.endSyscall();
3452 return;
3453 },
3454 .INTR => {
3455 try current_thread.checkCancel();
3456 continue;
3457 },
3458 .CANCELED => return current_thread.endSyscallCanceled(),
3459 else => |e| {
3460 current_thread.endSyscall();
3461 switch (e) {
3462 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3463 .INVAL => return error.Unseekable,
3464 .OVERFLOW => return error.Unseekable,
3465 .SPIPE => return error.Unseekable,
3466 .NXIO => return error.Unseekable,
3467 else => |err| return posix.unexpectedErrno(err),
3468 }
3469 },
8175 const reader_buffered = file_reader.interface.buffered();
8176 if (reader_buffered.len >= @intFromEnum(limit)) {
8177 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
8178 file_reader.interface.toss(n -| header.len);
8179 return n;
8180 }
8181 const out_fd = file.handle;
8182 const in_fd = file_reader.file.handle;
8183
8184 if (file_reader.size) |size| {
8185 if (size - file_reader.pos == 0) {
8186 if (reader_buffered.len != 0) {
8187 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
8188 file_reader.interface.toss(n -| header.len);
8189 return n;
8190 } else {
8191 return error.EndOfStream;
34708192 }
34718193 }
34728194 }
34738195
3474 if (native_os == .windows) {
3475 try current_thread.checkCancel();
3476 return windows.SetFilePointerEx_BEGIN(fd, offset);
3477 }
3478
3479 if (native_os == .wasi and !builtin.link_libc) while (true) {
3480 var new_offset: std.os.wasi.filesize_t = undefined;
3481 try current_thread.beginSyscall();
3482 switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
3483 .SUCCESS => {
3484 current_thread.endSyscall();
3485 return;
3486 },
3487 .INTR => {
3488 try current_thread.checkCancel();
3489 continue;
3490 },
3491 .CANCELED => return current_thread.endSyscallCanceled(),
3492 else => |e| {
3493 current_thread.endSyscall();
3494 switch (e) {
3495 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3496 .INVAL => return error.Unseekable,
3497 .OVERFLOW => return error.Unseekable,
3498 .SPIPE => return error.Unseekable,
3499 .NXIO => return error.Unseekable,
3500 .NOTCAPABLE => return error.AccessDenied,
3501 else => |err| return posix.unexpectedErrno(err),
3502 }
3503 },
8196 if (have_copy_file_range) cfr: {
8197 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr;
8198 if (header.len != 0 or reader_buffered.len != 0) {
8199 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
8200 file_reader.interface.toss(n -| header.len);
8201 return n;
35048202 }
3505 };
3506
3507 if (posix.SEEK == void) return error.Unseekable;
3508
3509 try current_thread.beginSyscall();
3510 while (true) {
3511 switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) {
3512 .SUCCESS => {
3513 current_thread.endSyscall();
3514 return;
3515 },
3516 .INTR => {
3517 try current_thread.checkCancel();
3518 continue;
8203 var off_in: i64 = undefined;
8204 const off_in_ptr: ?*i64 = switch (file_reader.mode) {
8205 .positional_simple, .streaming_simple => return error.Unimplemented,
8206 .positional => p: {
8207 off_in = @intCast(file_reader.pos);
8208 break :p &off_in;
8209 },
8210 .streaming => null,
8211 .failure => return error.ReadFailed,
8212 };
8213 var off_out: i64 = @intCast(offset);
8214 const current_thread = Thread.getCurrent(t);
8215 const n: usize = switch (native_os) {
8216 .linux => n: {
8217 try current_thread.beginSyscall();
8218 while (true) {
8219 const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0);
8220 switch (linux_copy_file_range_sys.errno(rc)) {
8221 .SUCCESS => {
8222 current_thread.endSyscall();
8223 break :n @intCast(rc);
8224 },
8225 .INTR => {
8226 try current_thread.checkCancel();
8227 continue;
8228 },
8229 .OPNOTSUPP, .INVAL, .NOSYS => {
8230 // Give calling code chance to observe before trying
8231 // something else.
8232 current_thread.endSyscall();
8233 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
8234 return 0;
8235 },
8236 else => |e| {
8237 current_thread.endSyscall();
8238 assert(error.Unexpected == switch (e) {
8239 .FBIG => return error.FileTooBig,
8240 .IO => return error.InputOutput,
8241 .NOMEM => return error.SystemResources,
8242 .NOSPC => return error.NoSpaceLeft,
8243 .OVERFLOW => return error.Unseekable,
8244 .NXIO => return error.Unseekable,
8245 .SPIPE => return error.Unseekable,
8246 .PERM => return error.PermissionDenied,
8247 .TXTBSY => return error.FileBusy,
8248 // copy_file_range can still work but not on
8249 // this pair of file descriptors.
8250 .XDEV => return error.Unimplemented,
8251 .ISDIR => |err| errnoBug(err),
8252 .BADF => |err| errnoBug(err),
8253 else => |err| posix.unexpectedErrno(err),
8254 });
8255 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
8256 return 0;
8257 },
8258 }
8259 }
35198260 },
3520 .CANCELED => return current_thread.endSyscallCanceled(),
3521 else => |e| {
3522 current_thread.endSyscall();
3523 switch (e) {
3524 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3525 .INVAL => return error.Unseekable,
3526 .OVERFLOW => return error.Unseekable,
3527 .SPIPE => return error.Unseekable,
3528 .NXIO => return error.Unseekable,
3529 else => |err| return posix.unexpectedErrno(err),
8261 .freebsd => n: {
8262 try current_thread.beginSyscall();
8263 while (true) {
8264 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0);
8265 switch (std.c.errno(rc)) {
8266 .SUCCESS => {
8267 current_thread.endSyscall();
8268 break :n @intCast(rc);
8269 },
8270 .INTR => {
8271 try current_thread.checkCancel();
8272 continue;
8273 },
8274 .OPNOTSUPP, .INVAL, .NOSYS => {
8275 // Give calling code chance to observe before trying
8276 // something else.
8277 current_thread.endSyscall();
8278 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
8279 return 0;
8280 },
8281 else => |e| {
8282 current_thread.endSyscall();
8283 assert(error.Unexpected == switch (e) {
8284 .FBIG => return error.FileTooBig,
8285 .IO => return error.InputOutput,
8286 .INTEGRITY => return error.CorruptedData,
8287 .NOSPC => return error.NoSpaceLeft,
8288 .OVERFLOW => return error.Unseekable,
8289 .NXIO => return error.Unseekable,
8290 .SPIPE => return error.Unseekable,
8291 .ISDIR => |err| errnoBug(err),
8292 .BADF => |err| errnoBug(err),
8293 else => |err| posix.unexpectedErrno(err),
8294 });
8295 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
8296 return 0;
8297 },
8298 }
35308299 }
35318300 },
8301 else => comptime unreachable,
8302 };
8303 if (n == 0) {
8304 file_reader.size = file_reader.pos;
8305 return error.EndOfStream;
8306 }
8307 file_reader.pos += n;
8308 return n;
8309 }
8310
8311 if (is_darwin) fcf: {
8312 if (@atomicLoad(UseFcopyfile, &t.use_fcopyfile, .monotonic) == .disabled) break :fcf;
8313 if (file_reader.pos != 0) break :fcf;
8314 if (offset != 0) break :fcf;
8315 if (limit != .unlimited) break :fcf;
8316 const size = file_reader.getSize() catch break :fcf;
8317 if (header.len != 0 or reader_buffered.len != 0) {
8318 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
8319 file_reader.interface.toss(n -| header.len);
8320 return n;
8321 }
8322 const current_thread = Thread.getCurrent(t);
8323 try current_thread.beginSyscall();
8324 while (true) {
8325 const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true });
8326 switch (posix.errno(rc)) {
8327 .SUCCESS => {
8328 current_thread.endSyscall();
8329 break;
8330 },
8331 .INTR => {
8332 try current_thread.checkCancel();
8333 continue;
8334 },
8335 .OPNOTSUPP => {
8336 // Give calling code chance to observe before trying
8337 // something else.
8338 current_thread.endSyscall();
8339 @atomicStore(UseFcopyfile, &t.use_fcopyfile, .disabled, .monotonic);
8340 return 0;
8341 },
8342 else => |e| {
8343 current_thread.endSyscall();
8344 assert(error.Unexpected == switch (e) {
8345 .NOMEM => return error.SystemResources,
8346 .INVAL => |err| errnoBug(err),
8347 else => |err| posix.unexpectedErrno(err),
8348 });
8349 return 0;
8350 },
8351 }
35328352 }
8353 file_reader.pos = size;
8354 return size;
35338355 }
3534}
3535
3536fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelfExeError!Io.File {
3537 const t: *Threaded = @ptrCast(@alignCast(userdata));
3538 switch (native_os) {
3539 .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags),
3540 .windows => {
3541 // If ImagePathName is a symlink, then it will contain the path of the symlink,
3542 // not the path that the symlink points to. However, because we are opening
3543 // the file, we can let the openFileW call follow the symlink for us.
3544 const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
3545 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
3546 const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name);
3547 return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags);
3548 },
3549 else => @panic("TODO implement openSelfExe"),
3550 }
3551}
3552
3553fn fileWritePositional(
3554 userdata: ?*anyopaque,
3555 file: Io.File,
3556 buffer: [][]const u8,
3557 offset: u64,
3558) Io.File.WritePositionalError!usize {
3559 const t: *Threaded = @ptrCast(@alignCast(userdata));
3560 _ = t;
3561 while (true) {
3562 _ = file;
3563 _ = buffer;
3564 _ = offset;
3565 @panic("TODO implement fileWritePositional");
3566 }
3567}
35688356
3569fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, buffer: [][]const u8) Io.File.WriteStreamingError!usize {
3570 const t: *Threaded = @ptrCast(@alignCast(userdata));
3571 _ = t;
3572 while (true) {
3573 _ = file;
3574 _ = buffer;
3575 @panic("TODO implement fileWriteStreaming");
3576 }
8357 return error.Unimplemented;
35778358}
35788359
35798360fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
......@@ -3673,7 +8454,6 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
36738454 try current_thread.checkCancel();
36748455 continue;
36758456 },
3676 .CANCELED => return current_thread.endSyscallCanceled(),
36778457 else => |e| {
36788458 current_thread.endSyscall();
36798459 switch (e) {
......@@ -3751,7 +8531,6 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
37518531 try current_thread.checkCancel();
37528532 continue;
37538533 },
3754 .CANCELED => return current_thread.endSyscallCanceled(),
37558534 // This prong handles success as well as unexpected errors.
37568535 else => return current_thread.endSyscall(),
37578536 }
......@@ -3825,7 +8604,6 @@ fn netListenIpPosix(
38258604 try current_thread.checkCancel();
38268605 continue;
38278606 },
3828 .CANCELED => return current_thread.endSyscallCanceled(),
38298607 else => |e| {
38308608 current_thread.endSyscall();
38318609 switch (e) {
......@@ -3989,7 +8767,6 @@ fn netListenUnixPosix(
39898767 try current_thread.checkCancel();
39908768 continue;
39918769 },
3992 .CANCELED => return current_thread.endSyscallCanceled(),
39938770 else => |e| {
39948771 current_thread.endSyscall();
39958772 switch (e) {
......@@ -4113,7 +8890,6 @@ fn posixBindUnix(
41138890 try current_thread.checkCancel();
41148891 continue;
41158892 },
4116 .CANCELED => return current_thread.endSyscallCanceled(),
41178893 else => |e| {
41188894 current_thread.endSyscall();
41198895 switch (e) {
......@@ -4158,7 +8934,6 @@ fn posixBind(
41588934 try current_thread.checkCancel();
41598935 continue;
41608936 },
4161 .CANCELED => return current_thread.endSyscallCanceled(),
41628937 else => |e| {
41638938 current_thread.endSyscall();
41648939 switch (e) {
......@@ -4194,7 +8969,6 @@ fn posixConnect(
41948969 try current_thread.checkCancel();
41958970 continue;
41968971 },
4197 .CANCELED => return current_thread.endSyscallCanceled(),
41988972 else => |e| {
41998973 current_thread.endSyscall();
42008974 switch (e) {
......@@ -4241,7 +9015,6 @@ fn posixConnectUnix(
42419015 try current_thread.checkCancel();
42429016 continue;
42439017 },
4244 .CANCELED => return current_thread.endSyscallCanceled(),
42459018 else => |e| {
42469019 current_thread.endSyscall();
42479020 switch (e) {
......@@ -4286,7 +9059,6 @@ fn posixGetSockName(
42869059 try current_thread.checkCancel();
42879060 continue;
42889061 },
4289 .CANCELED => return current_thread.endSyscallCanceled(),
42909062 else => |e| {
42919063 current_thread.endSyscall();
42929064 switch (e) {
......@@ -4354,7 +9126,6 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name
43549126 try current_thread.checkCancel();
43559127 continue;
43569128 },
4357 .CANCELED => return current_thread.endSyscallCanceled(),
43589129 else => |e| {
43599130 current_thread.endSyscall();
43609131 switch (e) {
......@@ -4684,7 +9455,6 @@ fn openSocketPosix(
46849455 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
46859456 .SUCCESS => break,
46869457 .INTR => continue,
4687 .CANCELED => return current_thread.endSyscallCanceled(),
46889458 else => |err| {
46899459 current_thread.endSyscall();
46909460 return posix.unexpectedErrno(err);
......@@ -4698,7 +9468,6 @@ fn openSocketPosix(
46989468 try current_thread.checkCancel();
46999469 continue;
47009470 },
4701 .CANCELED => return current_thread.endSyscallCanceled(),
47029471 else => |e| {
47039472 current_thread.endSyscall();
47049473 switch (e) {
......@@ -4800,7 +9569,6 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve
48009569 try current_thread.checkCancel();
48019570 continue;
48029571 },
4803 .CANCELED => return current_thread.endSyscallCanceled(),
48049572 else => |e| {
48059573 current_thread.endSyscall();
48069574 switch (e) {
......@@ -4909,7 +9677,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
49099677 try current_thread.checkCancel();
49109678 continue;
49119679 },
4912 .CANCELED => return current_thread.endSyscallCanceled(),
49139680 else => |e| {
49149681 current_thread.endSyscall();
49159682 switch (e) {
......@@ -4942,7 +9709,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
49429709 try current_thread.checkCancel();
49439710 continue;
49449711 },
4945 .CANCELED => return current_thread.endSyscallCanceled(),
49469712 else => |e| {
49479713 current_thread.endSyscall();
49489714 switch (e) {
......@@ -5178,7 +9944,6 @@ fn netSendOne(
51789944 try current_thread.checkCancel();
51799945 continue;
51809946 },
5181 .CANCELED => return current_thread.endSyscallCanceled(),
51829947 else => |e| {
51839948 current_thread.endSyscall();
51849949 switch (e) {
......@@ -5255,7 +10020,6 @@ fn netSendMany(
525510020 try current_thread.checkCancel();
525610021 continue;
525710022 },
5258 .CANCELED => return current_thread.endSyscallCanceled(),
525910023 else => |e| {
526010024 current_thread.endSyscall();
526110025 switch (e) {
......@@ -5388,7 +10152,6 @@ fn netReceivePosix(
538810152 continue :recv;
538910153 },
539010154 .INTR => continue,
5391 .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i },
539210155
539310156 .FAULT => |err| return .{ errnoBug(err), message_i },
539410157 .INVAL => |err| return .{ errnoBug(err), message_i },
......@@ -5397,7 +10160,6 @@ fn netReceivePosix(
539710160 }
539810161 },
539910162 .INTR => continue,
5400 .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i },
540110163
540210164 .BADF => |err| return .{ errnoBug(err), message_i },
540310165 .NFILE => return .{ error.SystemFdQuotaExceeded, message_i },
......@@ -5496,7 +10258,7 @@ fn netWritePosix(
549610258 addBuf(&iovecs, &msg.iovlen, splat_buffer);
549710259 remaining_splat -= splat_buffer.len;
549810260 }
5499 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]);
10261 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]);
550010262 },
550110263 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| {
550210264 addBuf(&iovecs, &msg.iovlen, pattern);
......@@ -5504,6 +10266,7 @@ fn netWritePosix(
550410266 },
550510267 };
550610268 const flags = posix.MSG.NOSIGNAL;
10269
550710270 try current_thread.beginSyscall();
550810271 while (true) {
550910272 const rc = posix.system.sendmsg(fd, &msg, flags);
......@@ -5516,7 +10279,6 @@ fn netWritePosix(
551610279 try current_thread.checkCancel();
551710280 continue;
551810281 },
5519 .CANCELED => return current_thread.endSyscallCanceled(),
552010282 else => |e| {
552110283 current_thread.endSyscall();
552210284 switch (e) {
......@@ -5580,7 +10342,7 @@ fn netWriteWindows(
558010342 addWsaBuf(&iovecs, &len, splat_buffer);
558110343 remaining_splat -= splat_buffer.len;
558210344 }
5583 addWsaBuf(&iovecs, &len, splat_buffer[0..remaining_splat]);
10345 addWsaBuf(&iovecs, &len, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]);
558410346 },
558510347 else => for (0..@min(splat, iovecs.len - len)) |_| {
558610348 addWsaBuf(&iovecs, &len, pattern);
......@@ -5614,8 +10376,7 @@ fn netWriteWindows(
561410376 else => |err| err,
561510377 };
561610378 switch (wsa_error) {
5617 .EINTR => continue,
5618 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(),
10379 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
561910380 .NOTINITIALISED => {
562010381 try initializeWsa(t);
562110382 continue;
......@@ -5667,7 +10428,14 @@ fn netWriteUnavailable(
566710428 return error.NetworkDown;
566810429}
566910430
5670fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {
10431/// This is either usize or u32. Since, either is fine, let's use the same
10432/// `addBuf` function for both writing to a file and sending network messages.
10433const iovlen_t = switch (native_os) {
10434 .wasi => u32,
10435 else => @FieldType(posix.msghdr_const, "iovlen"),
10436};
10437
10438fn addBuf(v: []posix.iovec_const, i: *iovlen_t, bytes: []const u8) void {
567110439 // OS checks ptr addr before length so zero length vectors must be omitted.
567210440 if (bytes.len == 0) return;
567310441 if (v.len - i.* == 0) return;
......@@ -5675,18 +10443,18 @@ fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"),
567510443 i.* += 1;
567610444}
567710445
5678fn netClose(userdata: ?*anyopaque, handle: net.Socket.Handle) void {
10446fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {
567910447 const t: *Threaded = @ptrCast(@alignCast(userdata));
568010448 _ = t;
568110449 switch (native_os) {
5682 .windows => closeSocketWindows(handle),
5683 else => posix.close(handle),
10450 .windows => for (handles) |handle| closeSocketWindows(handle),
10451 else => for (handles) |handle| posix.close(handle),
568410452 }
568510453}
568610454
5687fn netCloseUnavailable(userdata: ?*anyopaque, handle: net.Socket.Handle) void {
10455fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {
568810456 _ = userdata;
5689 _ = handle;
10457 _ = handles;
569010458 unreachable; // How you gonna close something that was impossible to open?
569110459}
569210460
......@@ -5727,7 +10495,6 @@ fn netInterfaceNameResolve(
572710495 try current_thread.checkCancel();
572810496 continue;
572910497 },
5730 .CANCELED => return current_thread.endSyscallCanceled(),
573110498 else => |e| {
573210499 current_thread.endSyscall();
573310500 switch (e) {
......@@ -5993,7 +10760,7 @@ fn netLookupFallible(
599310760 // TODO use dnsres_getaddrinfo
599410761 }
599510762
5996 if (native_os.isDarwin()) {
10763 if (is_darwin) {
599710764 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution
599810765 }
599910766
......@@ -6031,7 +10798,6 @@ fn netLookupFallible(
603110798 try current_thread.checkCancel();
603210799 continue;
603310800 },
6034 .CANCELED => return current_thread.endSyscallCanceled(),
603510801 else => |e| {
603610802 current_thread.endSyscall();
603710803 return posix.unexpectedErrno(e);
......@@ -6078,6 +10844,140 @@ fn netLookupFallible(
607810844 return error.OptionUnsupported;
607910845}
608010846
10847fn lockStderr(
10848 userdata: ?*anyopaque,
10849 buffer: []u8,
10850 terminal_mode: ?Io.Terminal.Mode,
10851) Io.Cancelable!Io.LockedStderr {
10852 const t: *Threaded = @ptrCast(@alignCast(userdata));
10853 // Only global mutex since this is Threaded.
10854 std.process.stderr_thread_mutex.lock();
10855 return initLockedStderr(t, buffer, terminal_mode);
10856}
10857
10858fn tryLockStderr(
10859 userdata: ?*anyopaque,
10860 buffer: []u8,
10861 terminal_mode: ?Io.Terminal.Mode,
10862) Io.Cancelable!?Io.LockedStderr {
10863 const t: *Threaded = @ptrCast(@alignCast(userdata));
10864 // Only global mutex since this is Threaded.
10865 if (!std.process.stderr_thread_mutex.tryLock()) return null;
10866 return try initLockedStderr(t, buffer, terminal_mode);
10867}
10868
10869fn initLockedStderr(
10870 t: *Threaded,
10871 buffer: []u8,
10872 terminal_mode: ?Io.Terminal.Mode,
10873) Io.Cancelable!Io.LockedStderr {
10874 if (!t.stderr_writer_initialized) {
10875 const io_t = ioBasic(t);
10876 if (is_windows) t.stderr_writer.file = .stderr();
10877 t.stderr_writer.io = io_t;
10878 t.stderr_writer_initialized = true;
10879 t.scanEnviron();
10880 const NO_COLOR = t.environ.exist.NO_COLOR;
10881 const CLICOLOR_FORCE = t.environ.exist.CLICOLOR_FORCE;
10882 t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file, NO_COLOR, CLICOLOR_FORCE);
10883 }
10884 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch |err| switch (err) {
10885 error.WriteFailed => switch (t.stderr_writer.err.?) {
10886 error.Canceled => |e| return e,
10887 else => {},
10888 },
10889 };
10890 t.stderr_writer.interface.flush() catch |err| switch (err) {
10891 error.WriteFailed => switch (t.stderr_writer.err.?) {
10892 error.Canceled => |e| return e,
10893 else => {},
10894 },
10895 };
10896 t.stderr_writer.interface.buffer = buffer;
10897 return .{
10898 .file_writer = &t.stderr_writer,
10899 .terminal_mode = terminal_mode orelse t.stderr_mode,
10900 };
10901}
10902
10903fn unlockStderr(userdata: ?*anyopaque) void {
10904 const t: *Threaded = @ptrCast(@alignCast(userdata));
10905 t.stderr_writer.interface.flush() catch |err| switch (err) {
10906 error.WriteFailed => switch (t.stderr_writer.err.?) {
10907 error.Canceled => recancel(t),
10908 else => {},
10909 },
10910 };
10911 t.stderr_writer.interface.end = 0;
10912 t.stderr_writer.interface.buffer = &.{};
10913 std.process.stderr_thread_mutex.unlock();
10914}
10915
10916fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentDirError!void {
10917 if (native_os == .wasi) return error.OperationUnsupported;
10918 const t: *Threaded = @ptrCast(@alignCast(userdata));
10919 const current_thread = Thread.getCurrent(t);
10920
10921 if (is_windows) {
10922 try current_thread.checkCancel();
10923 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
10924 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
10925 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);
10926 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;
10927 try current_thread.checkCancel();
10928 var nt_name: windows.UNICODE_STRING = .{
10929 .Length = path_len_bytes,
10930 .MaximumLength = path_len_bytes,
10931 .Buffer = @constCast(dir_path.ptr),
10932 };
10933 switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) {
10934 .SUCCESS => return,
10935 .OBJECT_NAME_INVALID => return error.BadPathName,
10936 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
10937 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
10938 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
10939 .INVALID_PARAMETER => |err| return windows.statusBug(err),
10940 .ACCESS_DENIED => return error.AccessDenied,
10941 .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),
10942 .NOT_A_DIRECTORY => return error.NotDir,
10943 else => |status| return windows.unexpectedStatus(status),
10944 }
10945 }
10946
10947 if (dir.handle == posix.AT.FDCWD) return;
10948
10949 try current_thread.beginSyscall();
10950 while (true) {
10951 switch (posix.errno(posix.system.fchdir(dir.handle))) {
10952 .SUCCESS => return current_thread.endSyscall(),
10953 .INTR => {
10954 try current_thread.checkCancel();
10955 continue;
10956 },
10957 .ACCES => {
10958 current_thread.endSyscall();
10959 return error.AccessDenied;
10960 },
10961 .BADF => |err| {
10962 current_thread.endSyscall();
10963 return errnoBug(err);
10964 },
10965 .NOTDIR => {
10966 current_thread.endSyscall();
10967 return error.NotDir;
10968 },
10969 .IO => {
10970 current_thread.endSyscall();
10971 return error.FileSystem;
10972 },
10973 else => |err| {
10974 current_thread.endSyscall();
10975 return posix.unexpectedErrno(err);
10976 },
10977 }
10978 }
10979}
10980
608110981pub const PosixAddress = extern union {
608210982 any: posix.sockaddr,
608310983 in: posix.sockaddr.in,
......@@ -6269,14 +11169,30 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t {
626911169 };
627011170}
627111171
6272fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat {
11172const linux_statx_mask: std.os.linux.STATX = .{
11173 .TYPE = true,
11174 .MODE = true,
11175 .ATIME = true,
11176 .MTIME = true,
11177 .CTIME = true,
11178 .INO = true,
11179 .SIZE = true,
11180 .NLINK = true,
11181};
11182
11183fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
11184 const actual_mask_int: u32 = @bitCast(stx.mask);
11185 const wanted_mask_int: u32 = @bitCast(linux_statx_mask);
11186 if ((actual_mask_int | wanted_mask_int) != actual_mask_int) return error.Unexpected;
11187
627311188 const atime = stx.atime;
627411189 const mtime = stx.mtime;
627511190 const ctime = stx.ctime;
627611191 return .{
627711192 .inode = stx.ino,
11193 .nlink = stx.nlink,
627811194 .size = stx.size,
6279 .mode = stx.mode,
11195 .permissions = .fromMode(stx.mode),
628011196 .kind = switch (stx.mode & std.os.linux.S.IFMT) {
628111197 std.os.linux.S.IFDIR => .directory,
628211198 std.os.linux.S.IFCHR => .character_device,
......@@ -6293,14 +11209,15 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat {
629311209 };
629411210}
629511211
6296fn statFromPosix(st: *const posix.Stat) Io.File.Stat {
11212fn statFromPosix(st: *const posix.Stat) File.Stat {
629711213 const atime = st.atime();
629811214 const mtime = st.mtime();
629911215 const ctime = st.ctime();
630011216 return .{
630111217 .inode = st.ino,
11218 .nlink = st.nlink,
630211219 .size = @bitCast(st.size),
6303 .mode = st.mode,
11220 .permissions = .fromMode(st.mode),
630411221 .kind = k: {
630511222 const m = st.mode & posix.S.IFMT;
630611223 switch (m) {
......@@ -6327,11 +11244,12 @@ fn statFromPosix(st: *const posix.Stat) Io.File.Stat {
632711244 };
632811245}
632911246
6330fn statFromWasi(st: *const std.os.wasi.filestat_t) Io.File.Stat {
11247fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat {
633111248 return .{
633211249 .inode = st.ino,
11250 .nlink = st.nlink,
633311251 .size = @bitCast(st.size),
6334 .mode = 0,
11252 .permissions = .default_file,
633511253 .kind = switch (st.filetype) {
633611254 .BLOCK_DEVICE => .block_device,
633711255 .CHARACTER_DEVICE => .character_device,
......@@ -6352,13 +11270,20 @@ fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp {
635211270}
635311271
635411272fn timestampToPosix(nanoseconds: i96) posix.timespec {
11273 if (builtin.zig_backend == .stage2_wasm) {
11274 // Workaround for https://codeberg.org/ziglang/zig/issues/30575
11275 return .{
11276 .sec = @intCast(@divTrunc(nanoseconds, std.time.ns_per_s)),
11277 .nsec = @intCast(@rem(nanoseconds, std.time.ns_per_s)),
11278 };
11279 }
635511280 return .{
635611281 .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)),
635711282 .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)),
635811283 };
635911284}
636011285
6361fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNameError![:0]u8 {
11286fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 {
636211287 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;
636311288 // >= rather than > to make room for the null byte
636411289 if (file_path.len >= buffer.len) return error.NameTooLong;
......@@ -6605,7 +11530,7 @@ fn lookupHosts(
660511530 options: HostName.LookupOptions,
660611531) !void {
660711532 const t_io = io(t);
6608 const file = Io.File.openAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) {
11533 const file = Dir.openFileAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) {
660911534 error.FileNotFound,
661011535 error.NotDir,
661111536 error.AccessDenied,
......@@ -6809,6 +11734,506 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {
680911734
681011735fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}
681111736
11737const pthreads_futex = struct {
11738 const c = std.c;
11739 const atomic = std.atomic;
11740
11741 const Event = struct {
11742 cond: c.pthread_cond_t,
11743 mutex: c.pthread_mutex_t,
11744 state: enum { empty, waiting, notified },
11745
11746 fn init(self: *Event) void {
11747 // Use static init instead of pthread_cond/mutex_init() since this is generally faster.
11748 self.cond = .{};
11749 self.mutex = .{};
11750 self.state = .empty;
11751 }
11752
11753 fn deinit(self: *Event) void {
11754 // Some platforms reportedly give EINVAL for statically initialized pthread types.
11755 const rc = c.pthread_cond_destroy(&self.cond);
11756 assert(rc == .SUCCESS or rc == .INVAL);
11757
11758 const rm = c.pthread_mutex_destroy(&self.mutex);
11759 assert(rm == .SUCCESS or rm == .INVAL);
11760
11761 self.* = undefined;
11762 }
11763
11764 fn wait(self: *Event, timeout: ?u64) error{Timeout}!void {
11765 assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS);
11766 defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS);
11767
11768 // Early return if the event was already set.
11769 if (self.state == .notified) {
11770 return;
11771 }
11772
11773 // Compute the absolute timeout if one was specified.
11774 // POSIX requires that REALTIME is used by default for the pthread timedwait functions.
11775 // This can be changed with pthread_condattr_setclock, but it's an extension and may not be available everywhere.
11776 var ts: c.timespec = undefined;
11777 if (timeout) |timeout_ns| {
11778 ts = std.posix.clock_gettime(c.CLOCK.REALTIME) catch return error.Timeout;
11779 ts.sec +|= @as(@TypeOf(ts.sec), @intCast(timeout_ns / std.time.ns_per_s));
11780 ts.nsec += @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s));
11781
11782 if (ts.nsec >= std.time.ns_per_s) {
11783 ts.sec +|= 1;
11784 ts.nsec -= std.time.ns_per_s;
11785 }
11786 }
11787
11788 // Start waiting on the event - there can be only one thread waiting.
11789 assert(self.state == .empty);
11790 self.state = .waiting;
11791
11792 while (true) {
11793 // Block using either pthread_cond_wait or pthread_cond_timewait if there's an absolute timeout.
11794 const rc = blk: {
11795 if (timeout == null) break :blk c.pthread_cond_wait(&self.cond, &self.mutex);
11796 break :blk c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts);
11797 };
11798
11799 // After waking up, check if the event was set.
11800 if (self.state == .notified) {
11801 return;
11802 }
11803
11804 assert(self.state == .waiting);
11805 switch (rc) {
11806 .SUCCESS => {},
11807 .TIMEDOUT => {
11808 // If timed out, reset the event to avoid the set() thread doing an unnecessary signal().
11809 self.state = .empty;
11810 return error.Timeout;
11811 },
11812 .INVAL => recoverableOsBugDetected(), // cond, mutex, and potentially ts should all be valid
11813 .PERM => recoverableOsBugDetected(), // mutex is locked when cond_*wait() functions are called
11814 else => recoverableOsBugDetected(),
11815 }
11816 }
11817 }
11818
11819 fn set(self: *Event) void {
11820 assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS);
11821 defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS);
11822
11823 // Make sure that multiple calls to set() were not done on the same Event.
11824 const old_state = self.state;
11825 assert(old_state != .notified);
11826
11827 // Mark the event as set and wake up the waiting thread if there was one.
11828 // This must be done while the mutex as the wait() thread could deallocate
11829 // the condition variable once it observes the new state, potentially causing a UAF if done unlocked.
11830 self.state = .notified;
11831 if (old_state == .waiting) {
11832 assert(c.pthread_cond_signal(&self.cond) == .SUCCESS);
11833 }
11834 }
11835 };
11836
11837 const Treap = std.Treap(usize, std.math.order);
11838 const Waiter = struct {
11839 node: Treap.Node,
11840 prev: ?*Waiter,
11841 next: ?*Waiter,
11842 tail: ?*Waiter,
11843 is_queued: bool,
11844 event: Event,
11845 };
11846
11847 // An unordered set of Waiters
11848 const WaitList = struct {
11849 top: ?*Waiter = null,
11850 len: usize = 0,
11851
11852 fn push(self: *WaitList, waiter: *Waiter) void {
11853 waiter.next = self.top;
11854 self.top = waiter;
11855 self.len += 1;
11856 }
11857
11858 fn pop(self: *WaitList) ?*Waiter {
11859 const waiter = self.top orelse return null;
11860 self.top = waiter.next;
11861 self.len -= 1;
11862 return waiter;
11863 }
11864 };
11865
11866 const WaitQueue = struct {
11867 fn insert(treap: *Treap, address: usize, waiter: *Waiter) void {
11868 // prepare the waiter to be inserted.
11869 waiter.next = null;
11870 waiter.is_queued = true;
11871
11872 // Find the wait queue entry associated with the address.
11873 // If there isn't a wait queue on the address, this waiter creates the queue.
11874 var entry = treap.getEntryFor(address);
11875 const entry_node = entry.node orelse {
11876 waiter.prev = null;
11877 waiter.tail = waiter;
11878 entry.set(&waiter.node);
11879 return;
11880 };
11881
11882 // There's a wait queue on the address; get the queue head and tail.
11883 const head: *Waiter = @fieldParentPtr("node", entry_node);
11884 const tail = head.tail orelse unreachable;
11885
11886 // Push the waiter to the tail by replacing it and linking to the previous tail.
11887 head.tail = waiter;
11888 tail.next = waiter;
11889 waiter.prev = tail;
11890 }
11891
11892 fn remove(treap: *Treap, address: usize, max_waiters: usize) WaitList {
11893 // Find the wait queue associated with this address and get the head/tail if any.
11894 var entry = treap.getEntryFor(address);
11895 var queue_head: ?*Waiter = if (entry.node) |node| @fieldParentPtr("node", node) else null;
11896 const queue_tail = if (queue_head) |head| head.tail else null;
11897
11898 // Once we're done updating the head, fix it's tail pointer and update the treap's queue head as well.
11899 defer entry.set(blk: {
11900 const new_head = queue_head orelse break :blk null;
11901 new_head.tail = queue_tail;
11902 break :blk &new_head.node;
11903 });
11904
11905 var removed = WaitList{};
11906 while (removed.len < max_waiters) {
11907 // dequeue and collect waiters from their wait queue.
11908 const waiter = queue_head orelse break;
11909 queue_head = waiter.next;
11910 removed.push(waiter);
11911
11912 // When dequeueing, we must mark is_queued as false.
11913 // This ensures that a waiter which calls tryRemove() returns false.
11914 assert(waiter.is_queued);
11915 waiter.is_queued = false;
11916 }
11917
11918 return removed;
11919 }
11920
11921 fn tryRemove(treap: *Treap, address: usize, waiter: *Waiter) bool {
11922 if (!waiter.is_queued) {
11923 return false;
11924 }
11925
11926 queue_remove: {
11927 // Find the wait queue associated with the address.
11928 var entry = blk: {
11929 // A waiter without a previous link means it's the queue head that's in the treap so we can avoid lookup.
11930 if (waiter.prev == null) {
11931 assert(waiter.node.key == address);
11932 break :blk treap.getEntryForExisting(&waiter.node);
11933 }
11934 break :blk treap.getEntryFor(address);
11935 };
11936
11937 // The queue head and tail must exist if we're removing a queued waiter.
11938 const head: *Waiter = @fieldParentPtr("node", entry.node orelse unreachable);
11939 const tail = head.tail orelse unreachable;
11940
11941 // A waiter with a previous link is never the head of the queue.
11942 if (waiter.prev) |prev| {
11943 assert(waiter != head);
11944 prev.next = waiter.next;
11945
11946 // A waiter with both a previous and next link is in the middle.
11947 // We only need to update the surrounding waiter's links to remove it.
11948 if (waiter.next) |next| {
11949 assert(waiter != tail);
11950 next.prev = waiter.prev;
11951 break :queue_remove;
11952 }
11953
11954 // A waiter with a previous but no next link means it's the tail of the queue.
11955 // In that case, we need to update the head's tail reference.
11956 assert(waiter == tail);
11957 head.tail = waiter.prev;
11958 break :queue_remove;
11959 }
11960
11961 // A waiter with no previous link means it's the queue head of queue.
11962 // We must replace (or remove) the head waiter reference in the treap.
11963 assert(waiter == head);
11964 entry.set(blk: {
11965 const new_head = waiter.next orelse break :blk null;
11966 new_head.tail = head.tail;
11967 break :blk &new_head.node;
11968 });
11969 }
11970
11971 // Mark the waiter as successfully removed.
11972 waiter.is_queued = false;
11973 return true;
11974 }
11975 };
11976
11977 const Bucket = struct {
11978 mutex: c.pthread_mutex_t align(atomic.cache_line) = .{},
11979 pending: atomic.Value(usize) = atomic.Value(usize).init(0),
11980 treap: Treap = .{},
11981
11982 // Global array of buckets that addresses map to.
11983 // Bucket array size is pretty much arbitrary here, but it must be a power of two for fibonacci hashing.
11984 var buckets = [_]Bucket{.{}} ** @bitSizeOf(usize);
11985
11986 // https://github.com/Amanieu/parking_lot/blob/1cf12744d097233316afa6c8b7d37389e4211756/core/src/parking_lot.rs#L343-L353
11987 fn from(address: usize) *Bucket {
11988 // The upper `@bitSizeOf(usize)` bits of the fibonacci golden ratio.
11989 // Hashing this via (h * k) >> (64 - b) where k=golden-ration and b=bitsize-of-array
11990 // evenly lays out h=hash values over the bit range even when the hash has poor entropy (identity-hash for pointers).
11991 const max_multiplier_bits = @bitSizeOf(usize);
11992 const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - max_multiplier_bits);
11993
11994 const max_bucket_bits = @ctz(buckets.len);
11995 comptime assert(std.math.isPowerOfTwo(buckets.len));
11996
11997 const index = (address *% fibonacci_multiplier) >> (max_multiplier_bits - max_bucket_bits);
11998 return &buckets[index];
11999 }
12000 };
12001
12002 const Address = struct {
12003 fn from(ptr: *const u32) usize {
12004 // Get the alignment of the pointer.
12005 const alignment = @alignOf(atomic.Value(u32));
12006 comptime assert(std.math.isPowerOfTwo(alignment));
12007
12008 // Make sure the pointer is aligned,
12009 // then cut off the zero bits from the alignment to get the unique address.
12010 const addr = @intFromPtr(ptr);
12011 assert(addr & (alignment - 1) == 0);
12012 return addr >> @ctz(@as(usize, alignment));
12013 }
12014 };
12015
12016 fn wait(ptr: *const u32, expect: u32, timeout: ?u64) error{Timeout}!void {
12017 const address = Address.from(ptr);
12018 const bucket = Bucket.from(address);
12019
12020 // Announce that there's a waiter in the bucket before checking the ptr/expect condition.
12021 // If the announcement is reordered after the ptr check, the waiter could deadlock:
12022 //
12023 // - T1: checks ptr == expect which is true
12024 // - T2: updates ptr to != expect
12025 // - T2: does Futex.wake(), sees no pending waiters, exits
12026 // - T1: bumps pending waiters (was reordered after the ptr == expect check)
12027 // - T1: goes to sleep and misses both the ptr change and T2's wake up
12028 //
12029 // acquire barrier to ensure the announcement happens before the ptr check below.
12030 var pending = bucket.pending.fetchAdd(1, .acquire);
12031 assert(pending < std.math.maxInt(usize));
12032
12033 // If the wait gets canceled, remove the pending count we previously added.
12034 // This is done outside the mutex lock to keep the critical section short in case of contention.
12035 var canceled = false;
12036 defer if (canceled) {
12037 pending = bucket.pending.fetchSub(1, .monotonic);
12038 assert(pending > 0);
12039 };
12040
12041 var waiter: Waiter = undefined;
12042 {
12043 assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
12044 defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
12045
12046 canceled = @atomicLoad(u32, ptr, .monotonic) != expect;
12047 if (canceled) {
12048 return;
12049 }
12050
12051 waiter.event.init();
12052 WaitQueue.insert(&bucket.treap, address, &waiter);
12053 }
12054
12055 defer {
12056 assert(!waiter.is_queued);
12057 waiter.event.deinit();
12058 }
12059
12060 waiter.event.wait(timeout) catch {
12061 // If we fail to cancel after a timeout, it means a wake() thread
12062 // dequeued us and will wake us up. We must wait until the event is
12063 // set as that's a signal that the wake() thread won't access the
12064 // waiter memory anymore. If we return early without waiting, the
12065 // waiter on the stack would be invalidated and the wake() thread
12066 // risks a UAF.
12067 defer if (!canceled) waiter.event.wait(null) catch unreachable;
12068
12069 assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
12070 defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
12071
12072 canceled = WaitQueue.tryRemove(&bucket.treap, address, &waiter);
12073 if (canceled) {
12074 return error.Timeout;
12075 }
12076 };
12077 }
12078
12079 fn wake(ptr: *const u32, max_waiters: u32) void {
12080 const address = Address.from(ptr);
12081 const bucket = Bucket.from(address);
12082
12083 // Quick check if there's even anything to wake up.
12084 // The change to the ptr's value must happen before we check for pending waiters.
12085 // If not, the wake() thread could miss a sleeping waiter and have it deadlock:
12086 //
12087 // - T2: p = has pending waiters (reordered before the ptr update)
12088 // - T1: bump pending waiters
12089 // - T1: if ptr == expected: sleep()
12090 // - T2: update ptr != expected
12091 // - T2: p is false from earlier so doesn't wake (T1 missed ptr update and T2 missed T1 sleeping)
12092 //
12093 // What we really want here is a Release load, but that doesn't exist under the C11 memory model.
12094 // We could instead do `bucket.pending.fetchAdd(0, Release) == 0` which achieves effectively the same thing,
12095 // LLVM lowers the fetchAdd(0, .release) into an mfence+load which avoids gaining ownership of the cache-line.
12096 if (bucket.pending.fetchAdd(0, .release) == 0) {
12097 return;
12098 }
12099
12100 // Keep a list of all the waiters notified and wake then up outside the mutex critical section.
12101 var notified = WaitList{};
12102 defer if (notified.len > 0) {
12103 const pending = bucket.pending.fetchSub(notified.len, .monotonic);
12104 assert(pending >= notified.len);
12105
12106 while (notified.pop()) |waiter| {
12107 assert(!waiter.is_queued);
12108 waiter.event.set();
12109 }
12110 };
12111
12112 assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS);
12113 defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS);
12114
12115 // Another pending check again to avoid the WaitQueue lookup if not necessary.
12116 if (bucket.pending.load(.monotonic) > 0) {
12117 notified = WaitQueue.remove(&bucket.treap, address, max_waiters);
12118 }
12119 }
12120};
12121
12122fn scanEnviron(t: *Threaded) void {
12123 t.mutex.lock();
12124 defer t.mutex.unlock();
12125
12126 if (t.environ.initialized) return;
12127 t.environ.initialized = true;
12128
12129 if (is_windows) {
12130 const ptr = windows.peb().ProcessParameters.Environment;
12131
12132 var i: usize = 0;
12133 while (ptr[i] != 0) {
12134 const key_start = i;
12135
12136 // There are some special environment variables that start with =,
12137 // so we need a special case to not treat = as a key/value separator
12138 // if it's the first character.
12139 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
12140 if (ptr[key_start] == '=') i += 1;
12141
12142 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
12143 const key_w = ptr[key_start..i];
12144 if (std.mem.eql(u16, key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) {
12145 t.environ.exist.NO_COLOR = true;
12146 } else if (std.mem.eql(u16, key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) {
12147 t.environ.exist.CLICOLOR_FORCE = true;
12148 }
12149 comptime assert(@sizeOf(Environ.String) == 0);
12150
12151 while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value
12152 i += 1; // skip over null byte
12153 }
12154 } else if (native_os == .wasi and !builtin.link_libc) {
12155 var environ_count: usize = undefined;
12156 var environ_buf_size: usize = undefined;
12157
12158 switch (std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size)) {
12159 .SUCCESS => {},
12160 else => |err| {
12161 t.environ.err = posix.unexpectedErrno(err);
12162 return;
12163 },
12164 }
12165 if (environ_count == 0) return;
12166
12167 const environ = t.allocator.alloc([*:0]u8, environ_count) catch |err| {
12168 t.environ.err = err;
12169 return;
12170 };
12171 defer t.allocator.free(environ);
12172 const environ_buf = t.allocator.alloc(u8, environ_buf_size) catch |err| {
12173 t.environ.err = err;
12174 return;
12175 };
12176 defer t.allocator.free(environ_buf);
12177
12178 switch (std.os.wasi.environ_get(environ.ptr, environ_buf.ptr)) {
12179 .SUCCESS => {},
12180 else => |err| {
12181 t.environ.err = posix.unexpectedErrno(err);
12182 return;
12183 },
12184 }
12185
12186 for (environ) |env| {
12187 const pair = std.mem.sliceTo(env, 0);
12188 var parts = std.mem.splitScalar(u8, pair, '=');
12189 const key = parts.first();
12190 if (std.mem.eql(u8, key, "NO_COLOR")) {
12191 t.environ.exist.NO_COLOR = true;
12192 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
12193 t.environ.exist.CLICOLOR_FORCE = true;
12194 }
12195 comptime assert(@sizeOf(Environ.String) == 0);
12196 }
12197 } else if (builtin.link_libc) {
12198 var ptr = std.c.environ;
12199 while (ptr[0]) |line| : (ptr += 1) {
12200 var line_i: usize = 0;
12201 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
12202 const key = line[0..line_i];
12203
12204 var end_i: usize = line_i;
12205 while (line[end_i] != 0) : (end_i += 1) {}
12206 const value = line[line_i + 1 .. end_i];
12207
12208 if (std.mem.eql(u8, key, "NO_COLOR")) {
12209 t.environ.exist.NO_COLOR = true;
12210 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
12211 t.environ.exist.CLICOLOR_FORCE = true;
12212 } else if (@hasField(Environ.String, "PATH") and std.mem.eql(u8, key, "PATH")) {
12213 t.environ.string.PATH = value;
12214 }
12215 }
12216 } else {
12217 for (t.environ.block) |line| {
12218 var line_i: usize = 0;
12219 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
12220 const key = line[0..line_i];
12221
12222 var end_i: usize = line_i;
12223 while (line[end_i] != 0) : (end_i += 1) {}
12224 const value = line[line_i + 1 .. end_i];
12225
12226 if (std.mem.eql(u8, key, "NO_COLOR")) {
12227 t.environ.exist.NO_COLOR = true;
12228 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
12229 t.environ.exist.CLICOLOR_FORCE = true;
12230 } else if (@hasField(Environ.String, "PATH") and std.mem.eql(u8, key, "PATH")) {
12231 t.environ.string.PATH = value;
12232 }
12233 }
12234 }
12235}
12236
681212237test {
681312238 _ = @import("Threaded/test.zig");
681412239}
lib/std/Io/Threaded/test.zig+7-5
......@@ -1,3 +1,5 @@
1//! Tests belong here if they access internal state of std.Io.Threaded or
2//! otherwise assume details of that particular implementation.
13const builtin = @import("builtin");
24
35const std = @import("std");
......@@ -11,7 +13,7 @@ test "concurrent vs main prevents deadlock via oversubscription" {
1113 return error.SkipZigTest;
1214 }
1315
14 var threaded: Io.Threaded = .init(std.testing.allocator);
16 var threaded: Io.Threaded = .init(std.testing.allocator, .{});
1517 defer threaded.deinit();
1618 const io = threaded.io();
1719
......@@ -44,7 +46,7 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" {
4446 return error.SkipZigTest;
4547 }
4648
47 var threaded: Io.Threaded = .init(std.testing.allocator);
49 var threaded: Io.Threaded = .init(std.testing.allocator, .{});
4850 defer threaded.deinit();
4951 const io = threaded.io();
5052
......@@ -78,7 +80,7 @@ test "async/concurrent context and result alignment" {
7880 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;
7981 var fba: std.heap.FixedBufferAllocator = .init(&buffer);
8082
81 var threaded: std.Io.Threaded = .init(fba.allocator());
83 var threaded: std.Io.Threaded = .init(fba.allocator(), .{});
8284 defer threaded.deinit();
8385 const io = threaded.io();
8486
......@@ -111,7 +113,7 @@ test "Group.async context alignment" {
111113 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;
112114 var fba: std.heap.FixedBufferAllocator = .init(&buffer);
113115
114 var threaded: std.Io.Threaded = .init(fba.allocator());
116 var threaded: std.Io.Threaded = .init(fba.allocator(), .{});
115117 defer threaded.deinit();
116118 const io = threaded.io();
117119
......@@ -131,7 +133,7 @@ fn returnArray() [32]u8 {
131133}
132134
133135test "async with array return type" {
134 var threaded: std.Io.Threaded = .init(std.testing.allocator);
136 var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});
135137 defer threaded.deinit();
136138 const io = threaded.io();
137139
lib/std/Io/Writer.zig+15-14
......@@ -1,7 +1,8 @@
1const Writer = @This();
2
13const builtin = @import("builtin");
24const native_endian = builtin.target.cpu.arch.endian();
35
4const Writer = @This();
56const std = @import("../std.zig");
67const assert = std.debug.assert;
78const Limit = std.Io.Limit;
......@@ -960,7 +961,7 @@ pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllE
960961 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
961962 error.EndOfStream => break,
962963 error.Unimplemented => {
963 file_reader.mode = file_reader.mode.toReading();
964 file_reader.mode = file_reader.mode.toSimple();
964965 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
965966 break;
966967 },
......@@ -2834,14 +2835,14 @@ test "discarding sendFile" {
28342835 var tmp_dir = testing.tmpDir(.{});
28352836 defer tmp_dir.cleanup();
28362837
2837 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2838 defer file.close();
2838 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2839 defer file.close(io);
28392840 var r_buffer: [256]u8 = undefined;
2840 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2841 var file_writer: File.Writer = .init(file, io, &r_buffer);
28412842 try file_writer.interface.writeByte('h');
28422843 try file_writer.interface.flush();
28432844
2844 var file_reader = file_writer.moveToReader(io);
2845 var file_reader = file_writer.moveToReader();
28452846 try file_reader.seekTo(0);
28462847
28472848 var w_buffer: [256]u8 = undefined;
......@@ -2856,14 +2857,14 @@ test "allocating sendFile" {
28562857 var tmp_dir = testing.tmpDir(.{});
28572858 defer tmp_dir.cleanup();
28582859
2859 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2860 defer file.close();
2860 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2861 defer file.close(io);
28612862 var r_buffer: [2]u8 = undefined;
2862 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2863 var file_writer: File.Writer = .init(file, io, &r_buffer);
28632864 try file_writer.interface.writeAll("abcd");
28642865 try file_writer.interface.flush();
28652866
2866 var file_reader = file_writer.moveToReader(io);
2867 var file_reader = file_writer.moveToReader();
28672868 try file_reader.seekTo(0);
28682869 try file_reader.interface.fill(2);
28692870
......@@ -2880,14 +2881,14 @@ test sendFileReading {
28802881 var tmp_dir = testing.tmpDir(.{});
28812882 defer tmp_dir.cleanup();
28822883
2883 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2884 defer file.close();
2884 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2885 defer file.close(io);
28852886 var r_buffer: [2]u8 = undefined;
2886 var file_writer: std.fs.File.Writer = .init(file, &r_buffer);
2887 var file_writer: File.Writer = .init(file, io, &r_buffer);
28872888 try file_writer.interface.writeAll("abcd");
28882889 try file_writer.interface.flush();
28892890
2890 var file_reader = file_writer.moveToReader(io);
2891 var file_reader = file_writer.moveToReader();
28912892 try file_reader.seekTo(0);
28922893 try file_reader.interface.fill(2);
28932894
lib/std/Io/net.zig+22-3
......@@ -1043,7 +1043,11 @@ pub const Socket = struct {
10431043
10441044 /// Leaves `address` in a valid state.
10451045 pub fn close(s: *const Socket, io: Io) void {
1046 io.vtable.netClose(io.userdata, s.handle);
1046 io.vtable.netClose(io.userdata, (&s.handle)[0..1]);
1047 }
1048
1049 pub fn closeMany(io: Io, sockets: []const Socket) void {
1050 io.vtable.netClose(io.userdata, sockets);
10471051 }
10481052
10491053 pub const SendError = error{
......@@ -1184,7 +1188,7 @@ pub const Stream = struct {
11841188 const max_iovecs_len = 8;
11851189
11861190 pub fn close(s: *const Stream, io: Io) void {
1187 io.vtable.netClose(io.userdata, s.socket.handle);
1191 io.vtable.netClose(io.userdata, (&s.socket.handle)[0..1]);
11881192 }
11891193
11901194 pub const Reader = struct {
......@@ -1256,6 +1260,7 @@ pub const Stream = struct {
12561260 interface: Io.Writer,
12571261 stream: Stream,
12581262 err: ?Error = null,
1263 write_file_err: ?WriteFileError = null,
12591264
12601265 pub const Error = error{
12611266 /// Another TCP Fast Open is already in progress.
......@@ -1285,12 +1290,19 @@ pub const Stream = struct {
12851290 SocketNotBound,
12861291 } || Io.UnexpectedError || Io.Cancelable;
12871292
1293 pub const WriteFileError = error{
1294 NetworkDown,
1295 } || Io.Cancelable || Io.UnexpectedError;
1296
12881297 pub fn init(stream: Stream, io: Io, buffer: []u8) Writer {
12891298 return .{
12901299 .io = io,
12911300 .stream = stream,
12921301 .interface = .{
1293 .vtable = &.{ .drain = drain },
1302 .vtable = &.{
1303 .drain = drain,
1304 .sendFile = sendFile,
1305 },
12941306 .buffer = buffer,
12951307 },
12961308 };
......@@ -1307,6 +1319,13 @@ pub const Stream = struct {
13071319 };
13081320 return io_w.consume(n);
13091321 }
1322
1323 fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
1324 _ = io_w;
1325 _ = file_reader;
1326 _ = limit;
1327 return error.Unimplemented; // TODO
1328 }
13101329 };
13111330
13121331 pub fn reader(stream: Stream, io: Io, buffer: []u8) Reader {
lib/std/Io/net/HostName.zig+1-1
......@@ -343,7 +343,7 @@ pub const ResolvConf = struct {
343343 .attempts = 2,
344344 };
345345
346 const file = Io.File.openAbsolute(io, "/etc/resolv.conf", .{}) catch |err| switch (err) {
346 const file = Io.Dir.openFileAbsolute(io, "/etc/resolv.conf", .{}) catch |err| switch (err) {
347347 error.FileNotFound,
348348 error.NotDir,
349349 error.AccessDenied,
lib/std/Io/net/test.zig+9-4
......@@ -232,8 +232,10 @@ test "listen on an in use port" {
232232fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
233233 if (builtin.os.tag == .wasi) return error.SkipZigTest;
234234
235 const io = testing.io;
236
235237 const connection = try net.tcpConnectToHost(allocator, name, port);
236 defer connection.close();
238 defer connection.close(io);
237239
238240 var buf: [100]u8 = undefined;
239241 const len = try connection.read(&buf);
......@@ -244,8 +246,10 @@ fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyer
244246fn testClient(addr: net.IpAddress) anyerror!void {
245247 if (builtin.os.tag == .wasi) return error.SkipZigTest;
246248
249 const io = testing.io;
250
247251 const socket_file = try net.tcpConnectToAddress(addr);
248 defer socket_file.close();
252 defer socket_file.close(io);
249253
250254 var buf: [100]u8 = undefined;
251255 const len = try socket_file.read(&buf);
......@@ -267,6 +271,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
267271 if (builtin.single_threaded) return error.SkipZigTest;
268272 if (!net.has_unix_sockets) return error.SkipZigTest;
269273 if (builtin.os.tag == .windows) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25983
274 if (builtin.cpu.arch == .mipsel) return error.SkipZigTest; // TODO
270275
271276 const io = testing.io;
272277
......@@ -274,7 +279,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
274279 defer testing.allocator.free(socket_path);
275280
276281 const socket_addr = try net.UnixAddress.init(socket_path);
277 defer std.fs.cwd().deleteFile(socket_path) catch {};
282 defer Io.Dir.cwd().deleteFile(io, socket_path) catch {};
278283
279284 var server = try socket_addr.listen(io, .{});
280285 defer server.socket.close(io);
......@@ -330,7 +335,7 @@ test "non-blocking tcp server" {
330335 try testing.expectError(error.WouldBlock, accept_err);
331336
332337 const socket_file = try net.tcpConnectToAddress(server.socket.address);
333 defer socket_file.close();
338 defer socket_file.close(io);
334339
335340 var stream = try server.accept(io);
336341 defer stream.close(io);
lib/std/Io/test.zig+108-52
......@@ -3,16 +3,17 @@ const native_endian = builtin.cpu.arch.endian();
33
44const std = @import("std");
55const Io = std.Io;
6const testing = std.testing;
7const expect = std.testing.expect;
8const expectEqual = std.testing.expectEqual;
9const expectError = std.testing.expectError;
106const DefaultPrng = std.Random.DefaultPrng;
117const mem = std.mem;
128const fs = std.fs;
13const File = std.fs.File;
9const File = std.Io.File;
1410const assert = std.debug.assert;
1511
12const testing = std.testing;
13const expect = std.testing.expect;
14const expectEqual = std.testing.expectEqual;
15const expectError = std.testing.expectError;
16const expectEqualStrings = std.testing.expectEqualStrings;
1617const tmpDir = std.testing.tmpDir;
1718
1819test "write a file, read it, then delete it" {
......@@ -27,10 +28,10 @@ test "write a file, read it, then delete it" {
2728 random.bytes(data[0..]);
2829 const tmp_file_name = "temp_test_file.txt";
2930 {
30 var file = try tmp.dir.createFile(tmp_file_name, .{});
31 defer file.close();
31 var file = try tmp.dir.createFile(io, tmp_file_name, .{});
32 defer file.close(io);
3233
33 var file_writer = file.writer(&.{});
34 var file_writer = file.writer(io, &.{});
3435 const st = &file_writer.interface;
3536 try st.print("begin", .{});
3637 try st.writeAll(&data);
......@@ -40,14 +41,14 @@ test "write a file, read it, then delete it" {
4041
4142 {
4243 // Make sure the exclusive flag is honored.
43 try expectError(File.OpenError.PathAlreadyExists, tmp.dir.createFile(tmp_file_name, .{ .exclusive = true }));
44 try expectError(File.OpenError.PathAlreadyExists, tmp.dir.createFile(io, tmp_file_name, .{ .exclusive = true }));
4445 }
4546
4647 {
47 var file = try tmp.dir.openFile(tmp_file_name, .{});
48 defer file.close();
48 var file = try tmp.dir.openFile(io, tmp_file_name, .{});
49 defer file.close(io);
4950
50 const file_size = try file.getEndPos();
51 const file_size = try file.length(io);
5152 const expected_file_size: u64 = "begin".len + data.len + "end".len;
5253 try expectEqual(expected_file_size, file_size);
5354
......@@ -60,71 +61,126 @@ test "write a file, read it, then delete it" {
6061 try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data));
6162 try expect(mem.eql(u8, contents[contents.len - "end".len ..], "end"));
6263 }
63 try tmp.dir.deleteFile(tmp_file_name);
64 try tmp.dir.deleteFile(io, tmp_file_name);
6465}
6566
66test "File seek ops" {
67test "File.Writer.seekTo" {
6768 var tmp = tmpDir(.{});
6869 defer tmp.cleanup();
6970
71 const io = testing.io;
72
73 var data: [8192]u8 = undefined;
74 @memset(&data, 0x55);
75
7076 const tmp_file_name = "temp_test_file.txt";
71 var file = try tmp.dir.createFile(tmp_file_name, .{});
72 defer file.close();
73
74 try file.writeAll(&([_]u8{0x55} ** 8192));
75
76 // Seek to the end
77 try file.seekFromEnd(0);
78 try expect((try file.getPos()) == try file.getEndPos());
79 // Negative delta
80 try file.seekBy(-4096);
81 try expect((try file.getPos()) == 4096);
82 // Positive delta
83 try file.seekBy(10);
84 try expect((try file.getPos()) == 4106);
85 // Absolute position
86 try file.seekTo(1234);
87 try expect((try file.getPos()) == 1234);
77 var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true });
78 defer file.close(io);
79
80 var fw = file.writerStreaming(io, &.{});
81
82 try fw.interface.writeAll(&data);
83 try expect(fw.logicalPos() == try file.length(io));
84 try fw.seekTo(1234);
85 try expect(fw.logicalPos() == 1234);
8886}
8987
90test "setEndPos" {
88test "File.setLength" {
89 const io = testing.io;
90
9191 var tmp = tmpDir(.{});
9292 defer tmp.cleanup();
9393
9494 const tmp_file_name = "temp_test_file.txt";
95 var file = try tmp.dir.createFile(tmp_file_name, .{});
96 defer file.close();
95 var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true });
96 defer file.close(io);
97
98 var fw = file.writerStreaming(io, &.{});
9799
98100 // Verify that the file size changes and the file offset is not moved
99 try expect((try file.getEndPos()) == 0);
100 try expect((try file.getPos()) == 0);
101 try file.setEndPos(8192);
102 try expect((try file.getEndPos()) == 8192);
103 try expect((try file.getPos()) == 0);
104 try file.seekTo(100);
105 try file.setEndPos(4096);
106 try expect((try file.getEndPos()) == 4096);
107 try expect((try file.getPos()) == 100);
108 try file.setEndPos(0);
109 try expect((try file.getEndPos()) == 0);
110 try expect((try file.getPos()) == 100);
101 try expect((try file.length(io)) == 0);
102 try expect(fw.logicalPos() == 0);
103 try file.setLength(io, 8192);
104 try expect((try file.length(io)) == 8192);
105 try expect(fw.logicalPos() == 0);
106 try fw.seekTo(100);
107 try file.setLength(io, 4096);
108 try expect((try file.length(io)) == 4096);
109 try expect(fw.logicalPos() == 100);
110 try file.setLength(io, 0);
111 try expect((try file.length(io)) == 0);
112 try expect(fw.logicalPos() == 100);
111113}
112114
113test "updateTimes" {
115test "legacy setLength" {
116 // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission)
117 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
118 if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23806
119
120 const io = testing.io;
121
122 var tmp = tmpDir(.{});
123 defer tmp.cleanup();
124
125 const file_name = "afile.txt";
126 try tmp.dir.writeFile(io, .{ .sub_path = file_name, .data = "ninebytes" });
127 const f = try tmp.dir.openFile(io, file_name, .{ .mode = .read_write });
128 defer f.close(io);
129
130 const initial_size = try f.length(io);
131 var buffer: [32]u8 = undefined;
132 var reader = f.reader(io, &.{});
133
134 {
135 try f.setLength(io, initial_size);
136 try expectEqual(initial_size, try f.length(io));
137 try reader.seekTo(0);
138 try expectEqual(initial_size, try reader.interface.readSliceShort(&buffer));
139 try expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]);
140 }
141
142 {
143 const larger = initial_size + 4;
144 try f.setLength(io, larger);
145 try expectEqual(larger, try f.length(io));
146 try reader.seekTo(0);
147 try expectEqual(larger, try reader.interface.readSliceShort(&buffer));
148 try expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]);
149 }
150
151 {
152 const smaller = initial_size - 5;
153 try f.setLength(io, smaller);
154 try expectEqual(smaller, try f.length(io));
155 try reader.seekTo(0);
156 try expectEqual(smaller, try reader.interface.readSliceShort(&buffer));
157 try expectEqualStrings("nine", buffer[0..@intCast(smaller)]);
158 }
159
160 try f.setLength(io, 0);
161 try expectEqual(0, try f.length(io));
162 try reader.seekTo(0);
163 try expectEqual(0, try reader.interface.readSliceShort(&buffer));
164}
165
166test "setTimestamps" {
167 const io = testing.io;
168
114169 var tmp = tmpDir(.{});
115170 defer tmp.cleanup();
116171
117172 const tmp_file_name = "just_a_temporary_file.txt";
118 var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true });
119 defer file.close();
173 var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true });
174 defer file.close(io);
120175
121 const stat_old = try file.stat();
176 const stat_old = try file.stat(io);
122177 // Set atime and mtime to 5s before
123 try file.updateTimes(
178 try file.setTimestamps(
179 io,
124180 stat_old.atime.subDuration(.fromSeconds(5)),
125181 stat_old.mtime.subDuration(.fromSeconds(5)),
126182 );
127 const stat_new = try file.stat();
183 const stat_new = try file.stat(io);
128184 try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds);
129185 try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);
130186}
lib/std/Io/tty.zig deleted-131
......@@ -1,131 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const File = std.fs.File;
4const process = std.process;
5const windows = std.os.windows;
6const native_os = builtin.os.tag;
7
8pub const Color = enum {
9 black,
10 red,
11 green,
12 yellow,
13 blue,
14 magenta,
15 cyan,
16 white,
17 bright_black,
18 bright_red,
19 bright_green,
20 bright_yellow,
21 bright_blue,
22 bright_magenta,
23 bright_cyan,
24 bright_white,
25 dim,
26 bold,
27 reset,
28};
29
30/// Provides simple functionality for manipulating the terminal in some way,
31/// such as coloring text, etc.
32pub const Config = union(enum) {
33 no_color,
34 escape_codes,
35 windows_api: if (native_os == .windows) WindowsContext else noreturn,
36
37 /// Detect suitable TTY configuration options for the given file (commonly stdout/stderr).
38 /// This includes feature checks for ANSI escape codes and the Windows console API, as well as
39 /// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default.
40 /// Will attempt to enable ANSI escape code support if necessary/possible.
41 pub fn detect(file: File) Config {
42 const force_color: ?bool = if (builtin.os.tag == .wasi)
43 null // wasi does not support environment variables
44 else if (process.hasNonEmptyEnvVarConstant("NO_COLOR"))
45 false
46 else if (process.hasNonEmptyEnvVarConstant("CLICOLOR_FORCE"))
47 true
48 else
49 null;
50
51 if (force_color == false) return .no_color;
52
53 if (file.getOrEnableAnsiEscapeSupport()) return .escape_codes;
54
55 if (native_os == .windows and file.isTty()) {
56 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
57 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) == windows.FALSE) {
58 return if (force_color == true) .escape_codes else .no_color;
59 }
60 return .{ .windows_api = .{
61 .handle = file.handle,
62 .reset_attributes = info.wAttributes,
63 } };
64 }
65
66 return if (force_color == true) .escape_codes else .no_color;
67 }
68
69 pub const WindowsContext = struct {
70 handle: File.Handle,
71 reset_attributes: u16,
72 };
73
74 pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || std.Io.Writer.Error;
75
76 pub fn setColor(conf: Config, w: *std.Io.Writer, color: Color) SetColorError!void {
77 nosuspend switch (conf) {
78 .no_color => return,
79 .escape_codes => {
80 const color_string = switch (color) {
81 .black => "\x1b[30m",
82 .red => "\x1b[31m",
83 .green => "\x1b[32m",
84 .yellow => "\x1b[33m",
85 .blue => "\x1b[34m",
86 .magenta => "\x1b[35m",
87 .cyan => "\x1b[36m",
88 .white => "\x1b[37m",
89 .bright_black => "\x1b[90m",
90 .bright_red => "\x1b[91m",
91 .bright_green => "\x1b[92m",
92 .bright_yellow => "\x1b[93m",
93 .bright_blue => "\x1b[94m",
94 .bright_magenta => "\x1b[95m",
95 .bright_cyan => "\x1b[96m",
96 .bright_white => "\x1b[97m",
97 .bold => "\x1b[1m",
98 .dim => "\x1b[2m",
99 .reset => "\x1b[0m",
100 };
101 try w.writeAll(color_string);
102 },
103 .windows_api => |ctx| {
104 const attributes = switch (color) {
105 .black => 0,
106 .red => windows.FOREGROUND_RED,
107 .green => windows.FOREGROUND_GREEN,
108 .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
109 .blue => windows.FOREGROUND_BLUE,
110 .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
111 .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
112 .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
113 .bright_black => windows.FOREGROUND_INTENSITY,
114 .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
115 .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
116 .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
117 .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
118 .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
119 .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
120 .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
121 // "dim" is not supported using basic character attributes, but let's still make it do *something*.
122 // This matches the old behavior of TTY.Color before the bright variants were added.
123 .dim => windows.FOREGROUND_INTENSITY,
124 .reset => ctx.reset_attributes,
125 };
126 try w.flush();
127 try windows.SetConsoleTextAttribute(ctx.handle, attributes);
128 },
129 };
130 }
131};
lib/std/Progress.zig+131-157
......@@ -1,26 +1,30 @@
11//! This API is non-allocating, non-fallible, thread-safe, and lock-free.
2const Progress = @This();
23
3const std = @import("std");
44const builtin = @import("builtin");
5const is_big_endian = builtin.cpu.arch.endian() == .big;
6const is_windows = builtin.os.tag == .windows;
7
8const std = @import("std");
9const Io = std.Io;
510const windows = std.os.windows;
611const testing = std.testing;
712const assert = std.debug.assert;
8const Progress = @This();
913const posix = std.posix;
10const is_big_endian = builtin.cpu.arch.endian() == .big;
11const is_windows = builtin.os.tag == .windows;
1214const Writer = std.Io.Writer;
1315
14/// `null` if the current node (and its children) should
15/// not print on update()
16terminal: std.fs.File,
16/// Currently this API only supports this value being set to stderr, which
17/// happens automatically inside `start`.
18terminal: Io.File,
19
20io: Io,
1721
1822terminal_mode: TerminalMode,
1923
20update_thread: ?std.Thread,
24update_worker: ?Io.Future(void),
2125
2226/// Atomically set by SIGWINCH as well as the root done() function.
23redraw_event: std.Thread.ResetEvent,
27redraw_event: Io.Event,
2428/// Indicates a request to shut down and reset global state.
2529/// Accessed atomically.
2630done: bool,
......@@ -48,6 +52,8 @@ node_freelist: Freelist,
4852/// value may at times temporarily exceed the node count.
4953node_end_index: u32,
5054
55start_failure: StartFailure,
56
5157pub const Status = enum {
5258 /// Indicates the application is progressing towards completion of a task.
5359 /// Unless the application is interactive, this is the only status the
......@@ -93,9 +99,9 @@ pub const Options = struct {
9399 /// Must be at least 200 bytes.
94100 draw_buffer: []u8 = &default_draw_buffer,
95101 /// How many nanoseconds between writing updates to the terminal.
96 refresh_rate_ns: u64 = 80 * std.time.ns_per_ms,
102 refresh_rate_ns: Io.Duration = .fromMilliseconds(80),
97103 /// How many nanoseconds to keep the output hidden
98 initial_delay_ns: u64 = 200 * std.time.ns_per_ms,
104 initial_delay_ns: Io.Duration = .fromMilliseconds(200),
99105 /// If provided, causes the progress item to have a denominator.
100106 /// 0 means unknown.
101107 estimated_total_items: usize = 0,
......@@ -121,20 +127,20 @@ pub const Node = struct {
121127 name: [max_name_len]u8 align(@alignOf(usize)),
122128
123129 /// Not thread-safe.
124 fn getIpcFd(s: Storage) ?posix.fd_t {
125 return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(posix.fd_t)) {
130 fn getIpcFd(s: Storage) ?Io.File.Handle {
131 return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) {
126132 .int => @bitCast(s.completed_count),
127133 .pointer => @ptrFromInt(s.completed_count),
128 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
134 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
129135 } else null;
130136 }
131137
132138 /// Thread-safe.
133 fn setIpcFd(s: *Storage, fd: posix.fd_t) void {
134 const integer: u32 = switch (@typeInfo(posix.fd_t)) {
139 fn setIpcFd(s: *Storage, fd: Io.File.Handle) void {
140 const integer: u32 = switch (@typeInfo(Io.File.Handle)) {
135141 .int => @bitCast(fd),
136142 .pointer => @intFromPtr(fd),
137 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
143 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
138144 };
139145 // `estimated_total_count` max int indicates the special state that
140146 // causes `completed_count` to be treated as a file descriptor, so
......@@ -327,13 +333,14 @@ pub const Node = struct {
327333 }
328334 } else {
329335 @atomicStore(bool, &global_progress.done, true, .monotonic);
330 global_progress.redraw_event.set();
331 if (global_progress.update_thread) |thread| thread.join();
336 const io = global_progress.io;
337 global_progress.redraw_event.set(io);
338 if (global_progress.update_worker) |*worker| worker.await(io);
332339 }
333340 }
334341
335342 /// Posix-only. Used by `std.process.Child`. Thread-safe.
336 pub fn setIpcFd(node: Node, fd: posix.fd_t) void {
343 pub fn setIpcFd(node: Node, fd: Io.File.Handle) void {
337344 const index = node.index.unwrap() orelse return;
338345 assert(fd >= 0);
339346 assert(fd != posix.STDOUT_FILENO);
......@@ -344,14 +351,14 @@ pub const Node = struct {
344351
345352 /// Posix-only. Thread-safe. Assumes the node is storing an IPC file
346353 /// descriptor.
347 pub fn getIpcFd(node: Node) ?posix.fd_t {
354 pub fn getIpcFd(node: Node) ?Io.File.Handle {
348355 const index = node.index.unwrap() orelse return null;
349356 const storage = storageByIndex(index);
350357 const int = @atomicLoad(u32, &storage.completed_count, .monotonic);
351 return switch (@typeInfo(posix.fd_t)) {
358 return switch (@typeInfo(Io.File.Handle)) {
352359 .int => @bitCast(int),
353360 .pointer => @ptrFromInt(int),
354 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
361 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
355362 };
356363 }
357364
......@@ -389,9 +396,10 @@ pub const Node = struct {
389396};
390397
391398var global_progress: Progress = .{
399 .io = undefined,
392400 .terminal = undefined,
393401 .terminal_mode = .off,
394 .update_thread = null,
402 .update_worker = null,
395403 .redraw_event = .unset,
396404 .refresh_rate_ns = undefined,
397405 .initial_delay_ns = undefined,
......@@ -401,6 +409,7 @@ var global_progress: Progress = .{
401409 .done = false,
402410 .need_clear = false,
403411 .status = .working,
412 .start_failure = .unstarted,
404413
405414 .node_parents = &node_parents_buffer,
406415 .node_storage = &node_storage_buffer,
......@@ -409,6 +418,13 @@ var global_progress: Progress = .{
409418 .node_end_index = 0,
410419};
411420
421pub const StartFailure = union(enum) {
422 unstarted,
423 spawn_ipc_worker: error{ConcurrencyUnavailable},
424 spawn_update_worker: error{ConcurrencyUnavailable},
425 parse_env_var: error{ InvalidCharacter, Overflow },
426};
427
412428const node_storage_buffer_len = 83;
413429var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;
414430var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;
......@@ -435,7 +451,9 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) {
435451/// Asserts there is only one global Progress instance.
436452///
437453/// Call `Node.end` when done.
438pub fn start(options: Options) Node {
454///
455/// If an error occurs, `start_failure` will be populated.
456pub fn start(io: Io, options: Options) Node {
439457 // Ensure there is only 1 global Progress object.
440458 if (global_progress.node_end_index != 0) {
441459 debug_start_trace.dump();
......@@ -450,21 +468,24 @@ pub fn start(options: Options) Node {
450468
451469 assert(options.draw_buffer.len >= 200);
452470 global_progress.draw_buffer = options.draw_buffer;
453 global_progress.refresh_rate_ns = options.refresh_rate_ns;
454 global_progress.initial_delay_ns = options.initial_delay_ns;
471 global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds());
472 global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds());
455473
456474 if (noop_impl)
457475 return Node.none;
458476
477 global_progress.io = io;
478
459479 if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| {
460 global_progress.update_thread = std.Thread.spawn(.{}, ipcThreadRun, .{
461 @as(posix.fd_t, switch (@typeInfo(posix.fd_t)) {
480 global_progress.update_worker = io.concurrent(ipcThreadRun, .{
481 io,
482 @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) {
462483 .int => ipc_fd,
463484 .pointer => @ptrFromInt(ipc_fd),
464 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
465 }),
485 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
486 } }),
466487 }) catch |err| {
467 std.log.warn("failed to spawn IPC thread for communicating progress to parent: {s}", .{@errorName(err)});
488 global_progress.start_failure = .{ .spawn_ipc_worker = err };
468489 return Node.none;
469490 };
470491 } else |env_err| switch (env_err) {
......@@ -472,14 +493,21 @@ pub fn start(options: Options) Node {
472493 if (options.disable_printing) {
473494 return Node.none;
474495 }
475 const stderr: std.fs.File = .stderr();
496 const stderr: Io.File = .stderr();
476497 global_progress.terminal = stderr;
477 if (stderr.getOrEnableAnsiEscapeSupport()) {
498 if (stderr.enableAnsiEscapeCodes(io)) |_| {
478499 global_progress.terminal_mode = .ansi_escape_codes;
479 } else if (is_windows and stderr.isTty()) {
480 global_progress.terminal_mode = TerminalMode{ .windows_api = .{
481 .code_page = windows.kernel32.GetConsoleOutputCP(),
482 } };
500 } else |_| if (is_windows) {
501 if (stderr.isTty(io)) |is_tty| {
502 if (is_tty) global_progress.terminal_mode = TerminalMode{ .windows_api = .{
503 .code_page = windows.kernel32.GetConsoleOutputCP(),
504 } };
505 } else |err| switch (err) {
506 error.Canceled => {
507 io.recancel();
508 return Node.none;
509 },
510 }
483511 }
484512
485513 if (global_progress.terminal_mode == .off) {
......@@ -497,17 +525,17 @@ pub fn start(options: Options) Node {
497525
498526 if (switch (global_progress.terminal_mode) {
499527 .off => unreachable, // handled a few lines above
500 .ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{}),
501 .windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{}) else unreachable,
502 }) |thread| {
503 global_progress.update_thread = thread;
528 .ansi_escape_codes => io.concurrent(updateThreadRun, .{io}),
529 .windows_api => if (is_windows) io.concurrent(windowsApiUpdateThreadRun, .{io}) else unreachable,
530 }) |future| {
531 global_progress.update_worker = future;
504532 } else |err| {
505 std.log.warn("unable to spawn thread for printing progress to terminal: {s}", .{@errorName(err)});
533 global_progress.start_failure = .{ .spawn_update_worker = err };
506534 return Node.none;
507535 }
508536 },
509537 else => |e| {
510 std.log.warn("invalid ZIG_PROGRESS file descriptor integer: {s}", .{@errorName(e)});
538 global_progress.start_failure = .{ .parse_env_var = e };
511539 return Node.none;
512540 },
513541 }
......@@ -521,48 +549,52 @@ pub fn setStatus(new_status: Status) void {
521549}
522550
523551/// Returns whether a resize is needed to learn the terminal size.
524fn wait(timeout_ns: u64) bool {
525 const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| true else |err| switch (err) {
526 error.Timeout => false,
552fn wait(io: Io, timeout_ns: u64) bool {
553 const timeout: Io.Timeout = .{ .duration = .{
554 .clock = .awake,
555 .raw = .fromNanoseconds(timeout_ns),
556 } };
557 const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) {
558 error.Timeout, error.Canceled => false,
527559 };
528560 global_progress.redraw_event.reset();
529561 return resize_flag or (global_progress.cols == 0);
530562}
531563
532fn updateThreadRun() void {
564fn updateThreadRun(io: Io) void {
533565 // Store this data in the thread so that it does not need to be part of the
534566 // linker data of the main executable.
535567 var serialized_buffer: Serialized.Buffer = undefined;
536568
537569 {
538 const resize_flag = wait(global_progress.initial_delay_ns);
570 const resize_flag = wait(io, global_progress.initial_delay_ns);
539571 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
540572 maybeUpdateSize(resize_flag);
541573
542574 const buffer, _ = computeRedraw(&serialized_buffer);
543 if (stderr_mutex.tryLock()) {
544 defer stderr_mutex.unlock();
545 write(buffer) catch return;
575 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
576 defer io.unlockStderr();
546577 global_progress.need_clear = true;
578 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
547579 }
548580 }
549581
550582 while (true) {
551 const resize_flag = wait(global_progress.refresh_rate_ns);
583 const resize_flag = wait(io, global_progress.refresh_rate_ns);
552584
553585 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
554 stderr_mutex.lock();
555 defer stderr_mutex.unlock();
556 return clearWrittenWithEscapeCodes() catch {};
586 const stderr = io.lockStderr(&.{}, null) catch return;
587 defer io.unlockStderr();
588 return clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
557589 }
558590
559591 maybeUpdateSize(resize_flag);
560592
561593 const buffer, _ = computeRedraw(&serialized_buffer);
562 if (stderr_mutex.tryLock()) {
563 defer stderr_mutex.unlock();
564 write(buffer) catch return;
594 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
595 defer io.unlockStderr();
565596 global_progress.need_clear = true;
597 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
566598 }
567599 }
568600}
......@@ -575,117 +607,72 @@ fn windowsApiWriteMarker() void {
575607 _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null);
576608}
577609
578fn windowsApiUpdateThreadRun() void {
610fn windowsApiUpdateThreadRun(io: Io) void {
579611 var serialized_buffer: Serialized.Buffer = undefined;
580612
581613 {
582 const resize_flag = wait(global_progress.initial_delay_ns);
614 const resize_flag = wait(io, global_progress.initial_delay_ns);
583615 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
584616 maybeUpdateSize(resize_flag);
585617
586618 const buffer, const nl_n = computeRedraw(&serialized_buffer);
587 if (stderr_mutex.tryLock()) {
588 defer stderr_mutex.unlock();
619 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
620 defer io.unlockStderr();
589621 windowsApiWriteMarker();
590 write(buffer) catch return;
591622 global_progress.need_clear = true;
623 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
592624 windowsApiMoveToMarker(nl_n) catch return;
593625 }
594626 }
595627
596628 while (true) {
597 const resize_flag = wait(global_progress.refresh_rate_ns);
629 const resize_flag = wait(io, global_progress.refresh_rate_ns);
598630
599631 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
600 stderr_mutex.lock();
601 defer stderr_mutex.unlock();
632 _ = io.lockStderr(&.{}, null) catch return;
633 defer io.unlockStderr();
602634 return clearWrittenWindowsApi() catch {};
603635 }
604636
605637 maybeUpdateSize(resize_flag);
606638
607639 const buffer, const nl_n = computeRedraw(&serialized_buffer);
608 if (stderr_mutex.tryLock()) {
609 defer stderr_mutex.unlock();
640 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
641 defer io.unlockStderr();
610642 clearWrittenWindowsApi() catch return;
611643 windowsApiWriteMarker();
612 write(buffer) catch return;
613644 global_progress.need_clear = true;
645 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
614646 windowsApiMoveToMarker(nl_n) catch return;
615647 }
616648 }
617649}
618650
619/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
620///
621/// During the lock, any `std.Progress` information is cleared from the terminal.
622///
623/// The lock is recursive; the same thread may hold the lock multiple times.
624pub fn lockStdErr() void {
625 stderr_mutex.lock();
626 clearWrittenWithEscapeCodes() catch {};
627}
628
629pub fn unlockStdErr() void {
630 stderr_mutex.unlock();
631}
632
633/// Protected by `stderr_mutex`.
634const stderr_writer: *Writer = &stderr_file_writer.interface;
635/// Protected by `stderr_mutex`.
636var stderr_file_writer: std.fs.File.Writer = .{
637 .interface = std.fs.File.Writer.initInterface(&.{}),
638 .file = if (is_windows) undefined else .stderr(),
639 .mode = .streaming,
640};
641
642/// Allows the caller to freely write to the returned `Writer`,
643/// initialized with `buffer`, until `unlockStderrWriter` is called.
644///
645/// During the lock, any `std.Progress` information is cleared from the terminal.
646///
647/// The lock is recursive; the same thread may hold the lock multiple times.
648pub fn lockStderrWriter(buffer: []u8) *Writer {
649 stderr_mutex.lock();
650 clearWrittenWithEscapeCodes() catch {};
651 if (is_windows) stderr_file_writer.file = .stderr();
652 stderr_writer.flush() catch {};
653 stderr_writer.buffer = buffer;
654 return stderr_writer;
655}
656
657pub fn unlockStderrWriter() void {
658 stderr_writer.flush() catch {};
659 stderr_writer.end = 0;
660 stderr_writer.buffer = &.{};
661 stderr_mutex.unlock();
662}
663
664fn ipcThreadRun(fd: posix.fd_t) anyerror!void {
651fn ipcThreadRun(io: Io, file: Io.File) void {
665652 // Store this data in the thread so that it does not need to be part of the
666653 // linker data of the main executable.
667654 var serialized_buffer: Serialized.Buffer = undefined;
668655
669656 {
670 _ = wait(global_progress.initial_delay_ns);
657 _ = wait(io, global_progress.initial_delay_ns);
671658
672659 if (@atomicLoad(bool, &global_progress.done, .monotonic))
673660 return;
674661
675662 const serialized = serialize(&serialized_buffer);
676 writeIpc(fd, serialized) catch |err| switch (err) {
663 writeIpc(io, file, serialized) catch |err| switch (err) {
677664 error.BrokenPipe => return,
678665 };
679666 }
680667
681668 while (true) {
682 _ = wait(global_progress.refresh_rate_ns);
669 _ = wait(io, global_progress.refresh_rate_ns);
683670
684671 if (@atomicLoad(bool, &global_progress.done, .monotonic))
685672 return;
686673
687674 const serialized = serialize(&serialized_buffer);
688 writeIpc(fd, serialized) catch |err| switch (err) {
675 writeIpc(io, file, serialized) catch |err| switch (err) {
689676 error.BrokenPipe => return,
690677 };
691678 }
......@@ -784,11 +771,10 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
784771 }
785772}
786773
787fn clearWrittenWithEscapeCodes() anyerror!void {
774pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error!void {
788775 if (noop_impl or !global_progress.need_clear) return;
789
776 try file_writer.interface.writeAll(clear ++ progress_remove);
790777 global_progress.need_clear = false;
791 try write(clear ++ progress_remove);
792778}
793779
794780/// U+25BA or ►
......@@ -948,11 +934,11 @@ const SavedMetadata = struct {
948934const Fd = enum(i32) {
949935 _,
950936
951 fn init(fd: posix.fd_t) Fd {
937 fn init(fd: Io.File.Handle) Fd {
952938 return @enumFromInt(if (is_windows) @as(isize, @bitCast(@intFromPtr(fd))) else fd);
953939 }
954940
955 fn get(fd: Fd) posix.fd_t {
941 fn get(fd: Fd) Io.File.Handle {
956942 return if (is_windows)
957943 @ptrFromInt(@as(usize, @bitCast(@as(isize, @intFromEnum(fd)))))
958944 else
......@@ -963,6 +949,7 @@ const Fd = enum(i32) {
963949var ipc_metadata_len: u8 = 0;
964950
965951fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize {
952 const io = global_progress.io;
966953 const ipc_metadata_fds_copy = &serialized_buffer.ipc_metadata_fds_copy;
967954 const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy;
968955 const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds;
......@@ -981,14 +968,14 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
981968 0..,
982969 ) |main_parent, *main_storage, main_index| {
983970 if (main_parent == .unused) continue;
984 const fd = main_storage.getIpcFd() orelse continue;
985 const opt_saved_metadata = findOld(fd, old_ipc_metadata_fds, old_ipc_metadata);
971 const file: Io.File = .{ .handle = main_storage.getIpcFd() orelse continue };
972 const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
986973 var bytes_read: usize = 0;
987974 while (true) {
988 const n = posix.read(fd, pipe_buf[bytes_read..]) catch |err| switch (err) {
975 const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) {
989976 error.WouldBlock => break,
990977 else => |e| {
991 std.log.debug("failed to read child progress data: {s}", .{@errorName(e)});
978 std.log.debug("failed to read child progress data: {t}", .{e});
992979 main_storage.completed_count = 0;
993980 main_storage.estimated_total_count = 0;
994981 continue :main_loop;
......@@ -1014,7 +1001,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
10141001 // Ignore all but the last message on the pipe.
10151002 var input: []u8 = pipe_buf[0..bytes_read];
10161003 if (input.len == 0) {
1017 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, fd);
1004 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, file.handle);
10181005 continue;
10191006 }
10201007
......@@ -1024,7 +1011,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
10241011 if (input.len < expected_bytes) {
10251012 // Ignore short reads. We'll handle the next full message when it comes instead.
10261013 const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len);
1027 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, fd);
1014 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, file.handle);
10281015 continue :main_loop;
10291016 }
10301017 if (input.len > expected_bytes) {
......@@ -1042,7 +1029,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
10421029 const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len));
10431030
10441031 // Remember in case the pipe is empty on next update.
1045 ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd);
1032 ipc_metadata_fds[ipc_metadata_len] = Fd.init(file.handle);
10461033 ipc_metadata[ipc_metadata_len] = .{
10471034 .remaining_read_trash_bytes = 0,
10481035 .start_index = @intCast(serialized_len),
......@@ -1100,7 +1087,7 @@ fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void {
11001087}
11011088
11021089fn findOld(
1103 ipc_fd: posix.fd_t,
1090 ipc_fd: Io.File.Handle,
11041091 old_metadata_fds: []Fd,
11051092 old_metadata: []SavedMetadata,
11061093) ?*SavedMetadata {
......@@ -1118,7 +1105,7 @@ fn useSavedIpcData(
11181105 main_index: usize,
11191106 opt_saved_metadata: ?*SavedMetadata,
11201107 remaining_read_trash_bytes: u16,
1121 fd: posix.fd_t,
1108 fd: Io.File.Handle,
11221109) usize {
11231110 const parents_copy = &serialized_buffer.parents_copy;
11241111 const storage_copy = &serialized_buffer.storage_copy;
......@@ -1415,13 +1402,9 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {
14151402 return nl_n + 2 < p.rows;
14161403}
14171404
1418fn write(buf: []const u8) anyerror!void {
1419 try global_progress.terminal.writeAll(buf);
1420}
1421
14221405var remaining_write_trash_bytes: usize = 0;
14231406
1424fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {
1407fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void {
14251408 // Byteswap if necessary to ensure little endian over the pipe. This is
14261409 // needed because the parent or child process might be running in qemu.
14271410 if (is_big_endian) for (serialized.storage) |*s| s.byteSwap();
......@@ -1432,11 +1415,7 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {
14321415 const storage = std.mem.sliceAsBytes(serialized.storage);
14331416 const parents = std.mem.sliceAsBytes(serialized.parents);
14341417
1435 var vecs: [3]posix.iovec_const = .{
1436 .{ .base = header.ptr, .len = header.len },
1437 .{ .base = storage.ptr, .len = storage.len },
1438 .{ .base = parents.ptr, .len = parents.len },
1439 };
1418 var vecs: [3][]const u8 = .{ header, storage, parents };
14401419
14411420 // Ensures the packet can fit in the pipe buffer.
14421421 const upper_bound_msg_len = 1 + node_storage_buffer_len * @sizeOf(Node.Storage) +
......@@ -1447,14 +1426,14 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {
14471426 // We do this in a separate write call to give a better chance for the
14481427 // writev below to be in a single packet.
14491428 const n = @min(parents.len, remaining_write_trash_bytes);
1450 if (posix.write(fd, parents[0..n])) |written| {
1429 if (io.vtable.fileWriteStreaming(io.userdata, file, &.{}, &.{parents[0..n]}, 1)) |written| {
14511430 remaining_write_trash_bytes -= written;
14521431 continue;
14531432 } else |err| switch (err) {
14541433 error.WouldBlock => return,
14551434 error.BrokenPipe => return error.BrokenPipe,
14561435 else => |e| {
1457 std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)});
1436 std.log.debug("failed to send progress to parent process: {t}", .{e});
14581437 return error.BrokenPipe;
14591438 },
14601439 }
......@@ -1462,7 +1441,7 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {
14621441
14631442 // If this write would block we do not want to keep trying, but we need to
14641443 // know if a partial message was written.
1465 if (writevNonblock(fd, &vecs)) |written| {
1444 if (writevNonblock(io, file, &vecs)) |written| {
14661445 const total = header.len + storage.len + parents.len;
14671446 if (written < total) {
14681447 remaining_write_trash_bytes = total - written;
......@@ -1471,13 +1450,13 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {
14711450 error.WouldBlock => {},
14721451 error.BrokenPipe => return error.BrokenPipe,
14731452 else => |e| {
1474 std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)});
1453 std.log.debug("failed to send progress to parent process: {t}", .{e});
14751454 return error.BrokenPipe;
14761455 },
14771456 }
14781457}
14791458
1480fn writevNonblock(fd: posix.fd_t, iov: []posix.iovec_const) posix.WriteError!usize {
1459fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error!usize {
14811460 var iov_index: usize = 0;
14821461 var written: usize = 0;
14831462 var total_written: usize = 0;
......@@ -1486,9 +1465,9 @@ fn writevNonblock(fd: posix.fd_t, iov: []posix.iovec_const) posix.WriteError!usi
14861465 written >= iov[iov_index].len
14871466 else
14881467 return total_written) : (iov_index += 1) written -= iov[iov_index].len;
1489 iov[iov_index].base += written;
1468 iov[iov_index].ptr += written;
14901469 iov[iov_index].len -= written;
1491 written = try posix.writev(fd, iov[iov_index..]);
1470 written = try io.vtable.fileWriteStreaming(io.userdata, file, &.{}, iov, 1);
14921471 if (written == 0) return total_written;
14931472 total_written += written;
14941473 }
......@@ -1538,7 +1517,7 @@ fn handleSigWinch(sig: posix.SIG, info: *const posix.siginfo_t, ctx_ptr: ?*anyop
15381517 _ = info;
15391518 _ = ctx_ptr;
15401519 assert(sig == .WINCH);
1541 global_progress.redraw_event.set();
1520 global_progress.redraw_event.set(global_progress.io);
15421521}
15431522
15441523const have_sigwinch = switch (builtin.os.tag) {
......@@ -1563,11 +1542,6 @@ const have_sigwinch = switch (builtin.os.tag) {
15631542 else => false,
15641543};
15651544
1566/// The primary motivation for recursive mutex here is so that a panic while
1567/// stderr mutex is held still dumps the stack trace and other debug
1568/// information.
1569var stderr_mutex = std.Thread.Mutex.Recursive.init;
1570
15711545fn copyAtomicStore(dest: []align(@alignOf(usize)) u8, src: []const u8) void {
15721546 assert(dest.len == src.len);
15731547 const chunked_len = dest.len / @sizeOf(usize);
lib/std/Random/benchmark.zig+4-2
......@@ -1,7 +1,9 @@
11// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
22
3const std = @import("std");
43const builtin = @import("builtin");
4
5const std = @import("std");
6const Io = std.Io;
57const time = std.time;
68const Timer = time.Timer;
79const Random = std.Random;
......@@ -123,7 +125,7 @@ fn mode(comptime x: comptime_int) comptime_int {
123125
124126pub fn main() !void {
125127 var stdout_buffer: [0x100]u8 = undefined;
126 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
128 var stdout_writer = Io.File.stdout().writer(&stdout_buffer);
127129 const stdout = &stdout_writer.interface;
128130
129131 var buffer: [1024]u8 = undefined;
lib/std/Thread.zig+19-17
......@@ -7,6 +7,7 @@ const target = builtin.target;
77const native_os = builtin.os.tag;
88
99const std = @import("std.zig");
10const Io = std.Io;
1011const math = std.math;
1112const assert = std.debug.assert;
1213const posix = std.posix;
......@@ -174,9 +175,9 @@ pub const SetNameError = error{
174175 Unsupported,
175176 Unexpected,
176177 InvalidWtf8,
177} || posix.PrctlError || posix.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError;
178} || posix.PrctlError || posix.WriteError || Io.File.OpenError || std.fmt.BufPrintError;
178179
179pub fn setName(self: Thread, name: []const u8) SetNameError!void {
180pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void {
180181 if (name.len > max_name_len) return error.NameTooLong;
181182
182183 const name_with_terminator = blk: {
......@@ -207,10 +208,10 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
207208 var buf: [32]u8 = undefined;
208209 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
209210
210 const file = try std.fs.cwd().openFile(path, .{ .mode = .write_only });
211 defer file.close();
211 const file = try Io.Dir.cwd().openFile(io, path, .{ .mode = .write_only });
212 defer file.close(io);
212213
213 try file.writeAll(name);
214 try file.writeStreamingAll(io, name);
214215 return;
215216 },
216217 .windows => {
......@@ -292,7 +293,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
292293pub const GetNameError = error{
293294 Unsupported,
294295 Unexpected,
295} || posix.PrctlError || posix.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError;
296} || posix.PrctlError || posix.ReadError || Io.File.OpenError || std.fmt.BufPrintError;
296297
297298/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
298299/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
......@@ -321,11 +322,10 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
321322 var buf: [32]u8 = undefined;
322323 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
323324
324 var threaded: std.Io.Threaded = .init_single_threaded;
325 const io = threaded.ioBasic();
325 const io = std.Options.debug_io;
326326
327 const file = try std.fs.cwd().openFile(path, .{});
328 defer file.close();
327 const file = try Io.Dir.cwd().openFile(io, path, .{});
328 defer file.close(io);
329329
330330 var file_reader = file.readerStreaming(io, &.{});
331331 const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) {
......@@ -1675,14 +1675,14 @@ const LinuxThreadImpl = struct {
16751675 }
16761676};
16771677
1678fn testThreadName(thread: *Thread) !void {
1678fn testThreadName(io: Io, thread: *Thread) !void {
16791679 const testCases = &[_][]const u8{
16801680 "mythread",
16811681 "b" ** max_name_len,
16821682 };
16831683
16841684 inline for (testCases) |tc| {
1685 try thread.setName(tc);
1685 try thread.setName(io, tc);
16861686
16871687 var name_buffer: [max_name_len:0]u8 = undefined;
16881688
......@@ -1697,6 +1697,8 @@ fn testThreadName(thread: *Thread) !void {
16971697test "setName, getName" {
16981698 if (builtin.single_threaded) return error.SkipZigTest;
16991699
1700 const io = testing.io;
1701
17001702 const Context = struct {
17011703 start_wait_event: ResetEvent = .unset,
17021704 test_done_event: ResetEvent = .unset,
......@@ -1710,11 +1712,11 @@ test "setName, getName" {
17101712 ctx.start_wait_event.wait();
17111713
17121714 switch (native_os) {
1713 .windows => testThreadName(&ctx.thread) catch |err| switch (err) {
1715 .windows => testThreadName(io, &ctx.thread) catch |err| switch (err) {
17141716 error.Unsupported => return error.SkipZigTest,
17151717 else => return err,
17161718 },
1717 else => try testThreadName(&ctx.thread),
1719 else => try testThreadName(io, &ctx.thread),
17181720 }
17191721
17201722 // Signal our test is done
......@@ -1734,14 +1736,14 @@ test "setName, getName" {
17341736
17351737 switch (native_os) {
17361738 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
1737 const res = thread.setName("foobar");
1739 const res = thread.setName(io, "foobar");
17381740 try std.testing.expectError(error.Unsupported, res);
17391741 },
1740 .windows => testThreadName(&thread) catch |err| switch (err) {
1742 .windows => testThreadName(io, &thread) catch |err| switch (err) {
17411743 error.Unsupported => return error.SkipZigTest,
17421744 else => return err,
17431745 },
1744 else => try testThreadName(&thread),
1746 else => try testThreadName(io, &thread),
17451747 }
17461748
17471749 context.thread_done_event.set();
lib/std/c.zig+7-5
......@@ -148,9 +148,10 @@ pub const dev_t = switch (native_os) {
148148pub const mode_t = switch (native_os) {
149149 .linux => linux.mode_t,
150150 .emscripten => emscripten.mode_t,
151 .openbsd, .haiku, .netbsd, .illumos, .wasi, .windows => u32,
151 .openbsd, .haiku, .netbsd, .illumos, .windows => u32,
152152 // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L44
153153 .freebsd, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .dragonfly, .serenity => u16,
154 .wasi => if (builtin.link_libc) u32 else u0, // WASI libc emulates mode.
154155 else => u0,
155156};
156157
......@@ -160,9 +161,10 @@ pub const nlink_t = switch (native_os) {
160161 .wasi => c_ulonglong,
161162 // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L45
162163 .freebsd, .serenity => u64,
163 .openbsd, .netbsd, .illumos => u32,
164 .openbsd, .netbsd, .dragonfly, .illumos => u32,
164165 .haiku => i32,
165 else => void,
166 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => u16,
167 else => u0,
166168};
167169
168170pub const uid_t = switch (native_os) {
......@@ -10608,7 +10610,7 @@ pub extern "c" fn munmap(addr: *align(page_size) const anyopaque, len: usize) c_
1060810610pub extern "c" fn mremap(addr: ?*align(page_size) const anyopaque, old_len: usize, new_len: usize, flags: MREMAP, ...) *anyopaque;
1060910611pub extern "c" fn mprotect(addr: *align(page_size) anyopaque, len: usize, prot: c_uint) c_int;
1061010612pub extern "c" fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) c_int;
10611pub extern "c" fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: c_int) c_int;
10613pub extern "c" fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: c_uint) c_int;
1061210614pub extern "c" fn unlink(path: [*:0]const u8) c_int;
1061310615pub extern "c" fn unlinkat(dirfd: fd_t, path: [*:0]const u8, flags: c_uint) c_int;
1061410616pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8;
......@@ -10740,7 +10742,7 @@ pub extern "c" fn free(?*anyopaque) void;
1074010742pub extern "c" fn futimes(fd: fd_t, times: ?*[2]timeval) c_int;
1074110743pub extern "c" fn utimes(path: [*:0]const u8, times: ?*[2]timeval) c_int;
1074210744
10743pub extern "c" fn utimensat(dirfd: fd_t, pathname: [*:0]const u8, times: ?*[2]timespec, flags: u32) c_int;
10745pub extern "c" fn utimensat(dirfd: fd_t, pathname: [*:0]const u8, times: ?*const [2]timespec, flags: u32) c_int;
1074410746pub extern "c" fn futimens(fd: fd_t, times: ?*const [2]timespec) c_int;
1074510747
1074610748pub extern "c" fn pthread_create(
lib/std/c/darwin.zig+1-1
......@@ -349,7 +349,7 @@ pub const VM = struct {
349349pub const exception_type_t = c_int;
350350
351351pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32;
352pub extern "c" fn _NSGetExecutablePath(buf: [*:0]u8, bufsize: *u32) c_int;
352pub extern "c" fn _NSGetExecutablePath(buf: [*]u8, bufsize: *u32) c_int;
353353pub extern "c" fn _dyld_image_count() u32;
354354pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;
355355pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize;
lib/std/c/freebsd.zig+1-1
......@@ -250,7 +250,7 @@ pub const kinfo_file = extern struct {
250250 /// Reserved for future cap_rights
251251 _cap_spare: u64,
252252 /// Path to file, if any.
253 path: [PATH_MAX - 1:0]u8,
253 path: [PATH_MAX]u8,
254254
255255 comptime {
256256 assert(@sizeOf(@This()) == KINFO_FILE_SIZE);
lib/std/crypto/Certificate/Bundle.zig+16-16
......@@ -9,8 +9,8 @@ const builtin = @import("builtin");
99
1010const std = @import("../../std.zig");
1111const Io = std.Io;
12const Dir = std.Io.Dir;
1213const assert = std.debug.assert;
13const fs = std.fs;
1414const mem = std.mem;
1515const crypto = std.crypto;
1616const Allocator = std.mem.Allocator;
......@@ -171,17 +171,17 @@ fn rescanWindows(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanW
171171 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
172172}
173173
174pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError;
174pub const AddCertsFromDirPathError = Io.File.OpenError || AddCertsFromDirError;
175175
176176pub fn addCertsFromDirPath(
177177 cb: *Bundle,
178178 gpa: Allocator,
179179 io: Io,
180 dir: fs.Dir,
180 dir: Io.Dir,
181181 sub_dir_path: []const u8,
182182) AddCertsFromDirPathError!void {
183 var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });
184 defer iterable_dir.close();
183 var iterable_dir = try dir.openDir(io, sub_dir_path, .{ .iterate = true });
184 defer iterable_dir.close(io);
185185 return addCertsFromDir(cb, gpa, io, iterable_dir);
186186}
187187
......@@ -192,27 +192,27 @@ pub fn addCertsFromDirPathAbsolute(
192192 now: Io.Timestamp,
193193 abs_dir_path: []const u8,
194194) AddCertsFromDirPathError!void {
195 assert(fs.path.isAbsolute(abs_dir_path));
196 var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });
197 defer iterable_dir.close();
195 assert(Dir.path.isAbsolute(abs_dir_path));
196 var iterable_dir = try Dir.openDirAbsolute(io, abs_dir_path, .{ .iterate = true });
197 defer iterable_dir.close(io);
198198 return addCertsFromDir(cb, gpa, io, now, iterable_dir);
199199}
200200
201201pub const AddCertsFromDirError = AddCertsFromFilePathError;
202202
203pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, iterable_dir: fs.Dir) AddCertsFromDirError!void {
203pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, iterable_dir: Io.Dir) AddCertsFromDirError!void {
204204 var it = iterable_dir.iterate();
205 while (try it.next()) |entry| {
205 while (try it.next(io)) |entry| {
206206 switch (entry.kind) {
207207 .file, .sym_link => {},
208208 else => continue,
209209 }
210210
211 try addCertsFromFilePath(cb, gpa, io, now, iterable_dir.adaptToNewApi(), entry.name);
211 try addCertsFromFilePath(cb, gpa, io, now, iterable_dir, entry.name);
212212 }
213213}
214214
215pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError || Io.Clock.Error;
215pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError || Io.Clock.Error;
216216
217217pub fn addCertsFromFilePathAbsolute(
218218 cb: *Bundle,
......@@ -221,8 +221,8 @@ pub fn addCertsFromFilePathAbsolute(
221221 now: Io.Timestamp,
222222 abs_file_path: []const u8,
223223) AddCertsFromFilePathError!void {
224 var file = try fs.openFileAbsolute(abs_file_path, .{});
225 defer file.close();
224 var file = try Io.Dir.openFileAbsolute(io, abs_file_path, .{});
225 defer file.close(io);
226226 var file_reader = file.reader(io, &.{});
227227 return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds());
228228}
......@@ -242,8 +242,8 @@ pub fn addCertsFromFilePath(
242242}
243243
244244pub const AddCertsFromFileError = Allocator.Error ||
245 fs.File.GetSeekPosError ||
246 fs.File.ReadError ||
245 Io.File.Reader.Error ||
246 Io.File.Reader.SizeError ||
247247 ParseCertError ||
248248 std.base64.Error ||
249249 error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker, Streaming };
lib/std/crypto/Certificate/Bundle/macos.zig+2-3
......@@ -6,7 +6,7 @@ const mem = std.mem;
66const Allocator = std.mem.Allocator;
77const Bundle = @import("../Bundle.zig");
88
9pub const RescanMacError = Allocator.Error || fs.File.OpenError || fs.File.ReadError || fs.File.SeekError || Bundle.ParseCertError || error{EndOfStream};
9pub const RescanMacError = Allocator.Error || Io.File.OpenError || Io.File.Reader.Error || Io.File.SeekError || Bundle.ParseCertError || error{EndOfStream};
1010
1111pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanMacError!void {
1212 cb.bytes.clearRetainingCapacity();
......@@ -17,9 +17,8 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanM
1717 "/Library/Keychains/System.keychain",
1818 };
1919
20 _ = io; // TODO migrate file system to use std.Io
2120 for (keychain_paths) |keychain_path| {
22 const bytes = std.fs.cwd().readFileAlloc(keychain_path, gpa, .limited(std.math.maxInt(u32))) catch |err| switch (err) {
21 const bytes = Io.Dir.cwd().readFileAlloc(io, keychain_path, gpa, .limited(std.math.maxInt(u32))) catch |err| switch (err) {
2322 error.StreamTooLong => return error.FileTooBig,
2423 else => |e| return e,
2524 };
lib/std/crypto/benchmark.zig+6-4
......@@ -1,10 +1,12 @@
11// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
22
3const std = @import("std");
43const builtin = @import("builtin");
4
5const std = @import("std");
6const Io = std.Io;
57const mem = std.mem;
68const time = std.time;
7const Timer = time.Timer;
9const Timer = std.time.Timer;
810const crypto = std.crypto;
911
1012const KiB = 1024;
......@@ -504,7 +506,7 @@ fn mode(comptime x: comptime_int) comptime_int {
504506pub fn main() !void {
505507 // Size of buffer is about size of printed message.
506508 var stdout_buffer: [0x100]u8 = undefined;
507 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
509 var stdout_writer = Io.File.stdout().writer(&stdout_buffer);
508510 const stdout = &stdout_writer.interface;
509511
510512 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
......@@ -554,7 +556,7 @@ pub fn main() !void {
554556 }
555557 }
556558
557 var io_threaded = std.Io.Threaded.init(arena_allocator);
559 var io_threaded = std.Io.Threaded.init(arena_allocator, .{});
558560 defer io_threaded.deinit();
559561 const io = io_threaded.io();
560562
lib/std/crypto/codecs/asn1/test.zig+3-3
......@@ -73,8 +73,8 @@ test AllTypes {
7373 try std.testing.expectEqualSlices(u8, encoded, buf);
7474
7575 // Use this to update test file.
76 // const dir = try std.fs.cwd().openDir("lib/std/crypto/asn1", .{});
77 // var file = try dir.createFile(path, .{});
78 // defer file.close();
76 // const dir = try Io.Dir.cwd().openDir(io, "lib/std/crypto/asn1", .{});
77 // var file = try dir.createFile(io, path, .{});
78 // defer file.close(io);
7979 // try file.writeAll(buf);
8080}
lib/std/crypto/tls.zig-1
......@@ -32,7 +32,6 @@
3232
3333const std = @import("../std.zig");
3434const Tls = @This();
35const net = std.net;
3635const mem = std.mem;
3736const crypto = std.crypto;
3837const assert = std.debug.assert;
lib/std/debug.zig+250-278
......@@ -1,14 +1,13 @@
11const std = @import("std.zig");
22const Io = std.Io;
33const Writer = std.Io.Writer;
4const tty = std.Io.tty;
54const math = std.math;
65const mem = std.mem;
76const posix = std.posix;
87const fs = std.fs;
98const testing = std.testing;
109const Allocator = mem.Allocator;
11const File = std.fs.File;
10const File = std.Io.File;
1211const windows = std.os.windows;
1312
1413const builtin = @import("builtin");
......@@ -60,7 +59,7 @@ pub const cpu_context = @import("debug/cpu_context.zig");
6059/// };
6160/// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's
6261/// /// return address, or 0 if the end of the stack has been reached.
63/// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) SelfInfoError!usize;
62/// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) SelfInfoError!usize;
6463/// ```
6564pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo"))
6665 root.debug.SelfInfo
......@@ -262,53 +261,54 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
262261 else => true,
263262};
264263
265/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
264/// Allows the caller to freely write to stderr until `unlockStderr` is called.
266265///
267266/// During the lock, any `std.Progress` information is cleared from the terminal.
268pub fn lockStdErr() void {
269 std.Progress.lockStdErr();
270}
271
272pub fn unlockStdErr() void {
273 std.Progress.unlockStdErr();
274}
275
276/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.
277267///
278/// During the lock, any `std.Progress` information is cleared from the terminal.
268/// The lock is recursive, so it is valid for the same thread to call
269/// `lockStderr` multiple times, allowing the panic handler to safely
270/// dump the stack trace and panic message even if the mutex was held at the
271/// panic site.
279272///
280/// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple
281/// times. The primary motivation is that this allows the panic handler to safely dump the stack
282/// trace and panic message even if the mutex was held at the panic site.
273/// The returned `Writer` does not need to be manually flushed: flushing is
274/// performed automatically when the matching `unlockStderr` call occurs.
283275///
284/// The returned `Writer` does not need to be manually flushed: flushing is performed automatically
285/// when the matching `unlockStderrWriter` call occurs.
286pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } {
287 const global = struct {
288 var conf: ?tty.Config = null;
276/// This is a low-level debugging primitive that bypasses the `Io` interface,
277/// writing directly to stderr using the most basic syscalls available. This
278/// function does not switch threads, switch stacks, or suspend.
279///
280/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the
281/// application's chosen `Io` implementation.
282pub fn lockStderr(buffer: []u8) Io.LockedStderr {
283 const io = std.Options.debug_io;
284 const prev = io.swapCancelProtection(.blocked);
285 defer _ = io.swapCancelProtection(prev);
286 return io.lockStderr(buffer, null) catch |err| switch (err) {
287 error.Canceled => unreachable, // Cancel protection enabled above.
289288 };
290 const w = std.Progress.lockStderrWriter(buffer);
291 // The stderr lock also locks access to `global.conf`.
292 if (global.conf == null) {
293 global.conf = .detect(.stderr());
294 }
295 return .{ w, global.conf.? };
296289}
297290
298pub fn unlockStderrWriter() void {
299 std.Progress.unlockStderrWriter();
291pub fn unlockStderr() void {
292 const io = std.Options.debug_io;
293 io.unlockStderr();
300294}
301295
302/// Print to stderr, silently returning on failure. Intended for use in "printf
303/// debugging". Use `std.log` functions for proper logging.
296/// Writes to stderr, ignoring errors.
297///
298/// This is a low-level debugging primitive that bypasses the `Io` interface,
299/// writing directly to stderr using the most basic syscalls available. This
300/// function does not switch threads, switch stacks, or suspend.
304301///
305302/// Uses a 64-byte buffer for formatted printing which is flushed before this
306303/// function returns.
304///
305/// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to
306/// integrate with the application's chosen `Io` implementation.
307307pub fn print(comptime fmt: []const u8, args: anytype) void {
308308 var buffer: [64]u8 = undefined;
309 const bw, _ = lockStderrWriter(&buffer);
310 defer unlockStderrWriter();
311 nosuspend bw.print(fmt, args) catch return;
309 const stderr = lockStderr(&buffer);
310 defer unlockStderr();
311 stderr.file_writer.interface.print(fmt, args) catch return;
312312}
313313
314314/// Marked `inline` to propagate a comptime-known error to callers.
......@@ -323,43 +323,44 @@ pub inline fn getSelfDebugInfo() !*SelfInfo {
323323/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
324324/// Obtains the stderr mutex while dumping.
325325pub fn dumpHex(bytes: []const u8) void {
326 const bw, const ttyconf = lockStderrWriter(&.{});
327 defer unlockStderrWriter();
328 dumpHexFallible(bw, ttyconf, bytes) catch {};
326 const stderr = lockStderr(&.{}).terminal();
327 defer unlockStderr();
328 dumpHexFallible(stderr, bytes) catch {};
329329}
330330
331331/// Prints a hexadecimal view of the bytes, returning any error that occurs.
332pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !void {
332pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void {
333 const w = t.writer;
333334 var chunks = mem.window(u8, bytes, 16, 16);
334335 while (chunks.next()) |window| {
335336 // 1. Print the address.
336337 const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10;
337 try tty_config.setColor(bw, .dim);
338 try t.setColor(.dim);
338339 // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.
339340 // Also, make sure all lines are aligned by padding the address.
340 try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
341 try tty_config.setColor(bw, .reset);
341 try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
342 try t.setColor(.reset);
342343
343344 // 2. Print the bytes.
344345 for (window, 0..) |byte, index| {
345 try bw.print("{X:0>2} ", .{byte});
346 if (index == 7) try bw.writeByte(' ');
346 try w.print("{X:0>2} ", .{byte});
347 if (index == 7) try w.writeByte(' ');
347348 }
348 try bw.writeByte(' ');
349 try w.writeByte(' ');
349350 if (window.len < 16) {
350351 var missing_columns = (16 - window.len) * 3;
351352 if (window.len < 8) missing_columns += 1;
352 try bw.splatByteAll(' ', missing_columns);
353 try w.splatByteAll(' ', missing_columns);
353354 }
354355
355356 // 3. Print the characters.
356357 for (window) |byte| {
357358 if (std.ascii.isPrint(byte)) {
358 try bw.writeByte(byte);
359 try w.writeByte(byte);
359360 } else {
360361 // Related: https://github.com/ziglang/zig/issues/7600
361 if (tty_config == .windows_api) {
362 try bw.writeByte('.');
362 if (t.mode == .windows_api) {
363 try w.writeByte('.');
363364 continue;
364365 }
365366
......@@ -367,24 +368,25 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !
367368 // We don't want to do this for all control codes because most control codes apart from
368369 // the ones that Zig has escape sequences for are likely not very useful to print as symbols.
369370 switch (byte) {
370 '\n' => try bw.writeAll("␊"),
371 '\r' => try bw.writeAll("␍"),
372 '\t' => try bw.writeAll("␉"),
373 else => try bw.writeByte('.'),
371 '\n' => try w.writeAll("␊"),
372 '\r' => try w.writeAll("␍"),
373 '\t' => try w.writeAll("␉"),
374 else => try w.writeByte('.'),
374375 }
375376 }
376377 }
377 try bw.writeByte('\n');
378 try w.writeByte('\n');
378379 }
379380}
380381
381382test dumpHexFallible {
383 const gpa = testing.allocator;
382384 const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 };
383 var aw: Writer.Allocating = .init(std.testing.allocator);
385 var aw: Writer.Allocating = .init(gpa);
384386 defer aw.deinit();
385387
386 try dumpHexFallible(&aw.writer, .no_color, bytes);
387 const expected = try std.fmt.allocPrint(std.testing.allocator,
388 try dumpHexFallible(.{ .writer = &aw.writer, .mode = .no_color }, bytes);
389 const expected = try std.fmt.allocPrint(gpa,
388390 \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........
389391 \\{x:0>[2]} 01 12 13 ...
390392 \\
......@@ -393,8 +395,8 @@ test dumpHexFallible {
393395 @intFromPtr(bytes.ptr) + 16,
394396 @sizeOf(usize) * 2,
395397 });
396 defer std.testing.allocator.free(expected);
397 try std.testing.expectEqualStrings(expected, aw.written());
398 defer gpa.free(expected);
399 try testing.expectEqualStrings(expected, aw.written());
398400}
399401
400402/// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic.
......@@ -409,7 +411,7 @@ pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *con
409411/// away, and in fact the optimizer is able to use the assertion in its
410412/// heuristics.
411413///
412/// Inside a test block, it is best to use the `std.testing` module rather than
414/// Inside a test block, it is best to use the `testing` module rather than
413415/// this function, because this function may not detect a test failure in
414416/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert
415417/// function is the correct function to use.
......@@ -483,10 +485,7 @@ const use_trap_panic = switch (builtin.zig_backend) {
483485};
484486
485487/// Dumps a stack trace to standard error, then aborts.
486pub fn defaultPanic(
487 msg: []const u8,
488 first_trace_addr: ?usize,
489) noreturn {
488pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
490489 @branchHint(.cold);
491490
492491 if (use_trap_panic) @trap();
......@@ -522,7 +521,7 @@ pub fn defaultPanic(
522521 }
523522 @trap();
524523 },
525 .cuda, .amdhsa => std.posix.abort(),
524 .cuda, .amdhsa => std.process.abort(),
526525 .plan9 => {
527526 var status: [std.os.plan9.ERRMAX]u8 = undefined;
528527 const len = @min(msg.len, status.len - 1);
......@@ -546,26 +545,27 @@ pub fn defaultPanic(
546545 _ = panicking.fetchAdd(1, .seq_cst);
547546
548547 trace: {
549 const stderr, const tty_config = lockStderrWriter(&.{});
550 defer unlockStderrWriter();
548 const stderr = lockStderr(&.{}).terminal();
549 defer unlockStderr();
550 const writer = stderr.writer;
551551
552552 if (builtin.single_threaded) {
553 stderr.print("panic: ", .{}) catch break :trace;
553 writer.print("panic: ", .{}) catch break :trace;
554554 } else {
555555 const current_thread_id = std.Thread.getCurrentId();
556 stderr.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
556 writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
557557 }
558 stderr.print("{s}\n", .{msg}) catch break :trace;
558 writer.print("{s}\n", .{msg}) catch break :trace;
559559
560560 if (@errorReturnTrace()) |t| if (t.index > 0) {
561 stderr.writeAll("error return context:\n") catch break :trace;
562 writeStackTrace(t, stderr, tty_config) catch break :trace;
563 stderr.writeAll("\nstack trace:\n") catch break :trace;
561 writer.writeAll("error return context:\n") catch break :trace;
562 writeStackTrace(t, stderr) catch break :trace;
563 writer.writeAll("\nstack trace:\n") catch break :trace;
564564 };
565565 writeCurrentStackTrace(.{
566566 .first_address = first_trace_addr orelse @returnAddress(),
567567 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
568 }, stderr, tty_config) catch break :trace;
568 }, stderr) catch break :trace;
569569 }
570570
571571 waitForOtherThreadToFinishPanicking();
......@@ -575,12 +575,13 @@ pub fn defaultPanic(
575575 // A panic happened while trying to print a previous panic message.
576576 // We're still holding the mutex but that's fine as we're going to
577577 // call abort().
578 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
578 const stderr = lockStderr(&.{}).terminal();
579 stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
579580 },
580581 else => {}, // Panicked while printing the recursive panic message.
581582 }
582583
583 posix.abort();
584 std.process.abort();
584585}
585586
586587/// Must be called only after adding 1 to `panicking`. There are three callsites.
......@@ -621,13 +622,16 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
621622 var it: StackIterator = .init(options.context);
622623 defer it.deinit();
623624 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
625
626 const io = std.Options.debug_io;
627
624628 var total_frames: usize = 0;
625629 var index: usize = 0;
626630 var wait_for = options.first_address;
627631 // Ideally, we would iterate the whole stack so that the `index` in the returned trace was
628632 // indicative of how many frames were skipped. However, this has a significant runtime cost
629633 // in some cases, so at least for now, we don't do that.
630 while (index < addr_buf.len) switch (it.next()) {
634 while (index < addr_buf.len) switch (it.next(io)) {
631635 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,
632636 .end => break,
633637 .frame => |ret_addr| {
......@@ -653,37 +657,36 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
653657/// Write the current stack trace to `writer`, annotated with source locations.
654658///
655659/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
656pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
657 var threaded: Io.Threaded = .init_single_threaded;
658 const io = threaded.ioBasic();
659
660pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void {
661 const writer = t.writer;
660662 if (!std.options.allow_stack_tracing) {
661 tty_config.setColor(writer, .dim) catch {};
663 t.setColor(.dim) catch {};
662664 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
663 tty_config.setColor(writer, .reset) catch {};
665 t.setColor(.reset) catch {};
664666 return;
665667 }
666668 const di_gpa = getDebugInfoAllocator();
667669 const di = getSelfDebugInfo() catch |err| switch (err) {
668670 error.UnsupportedTarget => {
669 tty_config.setColor(writer, .dim) catch {};
671 t.setColor(.dim) catch {};
670672 try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});
671 tty_config.setColor(writer, .reset) catch {};
673 t.setColor(.reset) catch {};
672674 return;
673675 },
674676 };
675677 var it: StackIterator = .init(options.context);
676678 defer it.deinit();
677679 if (!it.stratOk(options.allow_unsafe_unwind)) {
678 tty_config.setColor(writer, .dim) catch {};
680 t.setColor(.dim) catch {};
679681 try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});
680 tty_config.setColor(writer, .reset) catch {};
682 t.setColor(.reset) catch {};
681683 return;
682684 }
683685 var total_frames: usize = 0;
684686 var wait_for = options.first_address;
685687 var printed_any_frame = false;
686 while (true) switch (it.next()) {
688 const io = std.Options.debug_io;
689 while (true) switch (it.next(io)) {
687690 .switch_to_fp => |unwind_error| {
688691 switch (StackIterator.fp_usability) {
689692 .useless, .unsafe => {},
......@@ -700,31 +703,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
700703 error.Unexpected => "unexpected error",
701704 };
702705 if (it.stratOk(options.allow_unsafe_unwind)) {
703 tty_config.setColor(writer, .dim) catch {};
706 t.setColor(.dim) catch {};
704707 try writer.print(
705708 "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",
706709 .{ module_name, unwind_error.address, caption },
707710 );
708 tty_config.setColor(writer, .reset) catch {};
711 t.setColor(.reset) catch {};
709712 } else {
710 tty_config.setColor(writer, .dim) catch {};
713 t.setColor(.dim) catch {};
711714 try writer.print(
712715 "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",
713716 .{ module_name, unwind_error.address, caption },
714717 );
715 tty_config.setColor(writer, .reset) catch {};
718 t.setColor(.reset) catch {};
716719 return;
717720 }
718721 },
719722 .end => break,
720723 .frame => |ret_addr| {
721724 if (total_frames > 10_000) {
722 tty_config.setColor(writer, .dim) catch {};
725 t.setColor(.dim) catch {};
723726 try writer.print(
724727 "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",
725728 .{total_frames},
726729 );
727 tty_config.setColor(writer, .reset) catch {};
730 t.setColor(.reset) catch {};
728731 return;
729732 }
730733 total_frames += 1;
......@@ -734,7 +737,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
734737 }
735738 // `ret_addr` is the return address, which is *after* the function call.
736739 // Subtract 1 to get an address *in* the function call for a better source location.
737 try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
740 try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset);
738741 printed_any_frame = true;
739742 },
740743 };
......@@ -742,8 +745,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
742745}
743746/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.
744747pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
745 const stderr, const tty_config = lockStderrWriter(&.{});
746 defer unlockStderrWriter();
748 const stderr = lockStderr(&.{}).terminal();
749 defer unlockStderr();
747750 writeCurrentStackTrace(.{
748751 .first_address = a: {
749752 if (options.first_address) |a| break :a a;
......@@ -752,33 +755,30 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
752755 },
753756 .context = options.context,
754757 .allow_unsafe_unwind = options.allow_unsafe_unwind,
755 }, stderr, tty_config) catch |err| switch (err) {
758 }, stderr) catch |err| switch (err) {
756759 error.WriteFailed => {},
757760 };
758761}
759762
760763pub const FormatStackTrace = struct {
761764 stack_trace: StackTrace,
762 tty_config: tty.Config,
765 terminal_mode: Io.Terminal.Mode = .no_color,
763766
764 pub fn format(context: @This(), writer: *Io.Writer) Io.Writer.Error!void {
765 try writer.writeAll("\n");
766 try writeStackTrace(&context.stack_trace, writer, context.tty_config);
767 pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void {
768 try writer.writeByte('\n');
769 try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_mode });
767770 }
768771};
769772
770773/// Write a previously captured stack trace to `writer`, annotated with source locations.
771pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
774pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void {
775 const writer = t.writer;
772776 if (!std.options.allow_stack_tracing) {
773 tty_config.setColor(writer, .dim) catch {};
777 t.setColor(.dim) catch {};
774778 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
775 tty_config.setColor(writer, .reset) catch {};
779 t.setColor(.reset) catch {};
776780 return;
777781 }
778 // We use an independent Io implementation here in case there was a problem
779 // with the application's Io implementation itself.
780 var threaded: Io.Threaded = .init_single_threaded;
781 const io = threaded.ioBasic();
782782
783783 // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if
784784 // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.
......@@ -787,29 +787,30 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.C
787787 const di_gpa = getDebugInfoAllocator();
788788 const di = getSelfDebugInfo() catch |err| switch (err) {
789789 error.UnsupportedTarget => {
790 tty_config.setColor(writer, .dim) catch {};
790 t.setColor(.dim) catch {};
791791 try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});
792 tty_config.setColor(writer, .reset) catch {};
792 t.setColor(.reset) catch {};
793793 return;
794794 },
795795 };
796 const io = std.Options.debug_io;
796797 const captured_frames = @min(n_frames, st.instruction_addresses.len);
797798 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
798799 // `ret_addr` is the return address, which is *after* the function call.
799800 // Subtract 1 to get an address *in* the function call for a better source location.
800 try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
801 try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset);
801802 }
802803 if (n_frames > captured_frames) {
803 tty_config.setColor(writer, .bold) catch {};
804 t.setColor(.bold) catch {};
804805 try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});
805 tty_config.setColor(writer, .reset) catch {};
806 t.setColor(.reset) catch {};
806807 }
807808}
808809/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
809810pub fn dumpStackTrace(st: *const StackTrace) void {
810 const stderr, const tty_config = lockStderrWriter(&.{});
811 defer unlockStderrWriter();
812 writeStackTrace(st, stderr, tty_config) catch |err| switch (err) {
811 const stderr = lockStderr(&.{}).terminal();
812 defer unlockStderr();
813 writeStackTrace(st, stderr) catch |err| switch (err) {
813814 error.WriteFailed => {},
814815 };
815816}
......@@ -960,7 +961,7 @@ const StackIterator = union(enum) {
960961 },
961962 };
962963
963 fn next(it: *StackIterator) Result {
964 fn next(it: *StackIterator, io: Io) Result {
964965 switch (it.*) {
965966 .ctx_first => |context_ptr| {
966967 // After the first frame, start actually unwinding.
......@@ -976,7 +977,7 @@ const StackIterator = union(enum) {
976977 .di => |*unwind_context| {
977978 const di = getSelfDebugInfo() catch unreachable;
978979 const di_gpa = getDebugInfoAllocator();
979 const ret_addr = di.unwindFrame(di_gpa, unwind_context) catch |err| {
980 const ret_addr = di.unwindFrame(di_gpa, io, unwind_context) catch |err| {
980981 const pc = unwind_context.pc;
981982 const fp = unwind_context.getFp();
982983 it.* = .{ .fp = fp };
......@@ -1104,170 +1105,146 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
11041105 return ptr;
11051106}
11061107
1107fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
1108fn printSourceAtAddress(
1109 gpa: Allocator,
1110 io: Io,
1111 debug_info: *SelfInfo,
1112 t: Io.Terminal,
1113 address: usize,
1114) Writer.Error!void {
11081115 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
11091116 error.MissingDebugInfo,
11101117 error.UnsupportedDebugInfo,
11111118 error.InvalidDebugInfo,
11121119 => .unknown,
11131120 error.ReadFailed, error.Unexpected, error.Canceled => s: {
1114 tty_config.setColor(writer, .dim) catch {};
1115 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1116 tty_config.setColor(writer, .reset) catch {};
1121 t.setColor(.dim) catch {};
1122 try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1123 t.setColor(.reset) catch {};
11171124 break :s .unknown;
11181125 },
11191126 error.OutOfMemory => s: {
1120 tty_config.setColor(writer, .dim) catch {};
1121 try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1122 tty_config.setColor(writer, .reset) catch {};
1127 t.setColor(.dim) catch {};
1128 try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1129 t.setColor(.reset) catch {};
11231130 break :s .unknown;
11241131 },
11251132 };
11261133 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
11271134 return printLineInfo(
1128 writer,
1135 io,
1136 t,
11291137 symbol.source_location,
11301138 address,
11311139 symbol.name orelse "???",
11321140 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
1133 tty_config,
11341141 );
11351142}
11361143fn printLineInfo(
1137 writer: *Writer,
1144 io: Io,
1145 t: Io.Terminal,
11381146 source_location: ?SourceLocation,
11391147 address: usize,
11401148 symbol_name: []const u8,
11411149 compile_unit_name: []const u8,
1142 tty_config: tty.Config,
11431150) Writer.Error!void {
1144 nosuspend {
1145 tty_config.setColor(writer, .bold) catch {};
1151 const writer = t.writer;
1152 t.setColor(.bold) catch {};
11461153
1147 if (source_location) |*sl| {
1148 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
1149 } else {
1150 try writer.writeAll("???:?:?");
1151 }
1154 if (source_location) |*sl| {
1155 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
1156 } else {
1157 try writer.writeAll("???:?:?");
1158 }
11521159
1153 tty_config.setColor(writer, .reset) catch {};
1154 try writer.writeAll(": ");
1155 tty_config.setColor(writer, .dim) catch {};
1156 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
1157 tty_config.setColor(writer, .reset) catch {};
1158 try writer.writeAll("\n");
1159
1160 // Show the matching source code line if possible
1161 if (source_location) |sl| {
1162 if (printLineFromFile(writer, sl)) {
1163 if (sl.column > 0) {
1164 // The caret already takes one char
1165 const space_needed = @as(usize, @intCast(sl.column - 1));
1166
1167 try writer.splatByteAll(' ', space_needed);
1168 tty_config.setColor(writer, .green) catch {};
1169 try writer.writeAll("^");
1170 tty_config.setColor(writer, .reset) catch {};
1171 }
1172 try writer.writeAll("\n");
1173 } else |_| {
1174 // Ignore all errors; it's a better UX to just print the source location without the
1175 // corresponding line number. The user can always open the source file themselves.
1160 t.setColor(.reset) catch {};
1161 try writer.writeAll(": ");
1162 t.setColor(.dim) catch {};
1163 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
1164 t.setColor(.reset) catch {};
1165 try writer.writeAll("\n");
1166
1167 // Show the matching source code line if possible
1168 if (source_location) |sl| {
1169 if (printLineFromFile(io, writer, sl)) {
1170 if (sl.column > 0) {
1171 // The caret already takes one char
1172 const space_needed = @as(usize, @intCast(sl.column - 1));
1173
1174 try writer.splatByteAll(' ', space_needed);
1175 t.setColor(.green) catch {};
1176 try writer.writeAll("^");
1177 t.setColor(.reset) catch {};
11761178 }
1179 try writer.writeAll("\n");
1180 } else |_| {
1181 // Ignore all errors; it's a better UX to just print the source location without the
1182 // corresponding line number. The user can always open the source file themselves.
11771183 }
11781184 }
11791185}
1180fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
1186fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !void {
11811187 // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`.
11821188 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) {
1183 return root.debug.printLineFromFile(writer, source_location);
1189 return root.debug.printLineFromFile(io, writer, source_location);
11841190 }
11851191
11861192 // Need this to always block even in async I/O mode, because this could potentially
11871193 // be called from e.g. the event loop code crashing.
1188 var f = try fs.cwd().openFile(source_location.file_name, .{});
1189 defer f.close();
1190 // TODO fstat and make sure that the file has the correct size
1191
1192 var buf: [4096]u8 = undefined;
1193 var amt_read = try f.read(buf[0..]);
1194 const line_start = seek: {
1195 var current_line_start: usize = 0;
1196 var next_line: usize = 1;
1197 while (next_line != source_location.line) {
1198 const slice = buf[current_line_start..amt_read];
1199 if (mem.findScalar(u8, slice, '\n')) |pos| {
1200 next_line += 1;
1201 if (pos == slice.len - 1) {
1202 amt_read = try f.read(buf[0..]);
1203 current_line_start = 0;
1204 } else current_line_start += pos + 1;
1205 } else if (amt_read < buf.len) {
1206 return error.EndOfFile;
1207 } else {
1208 amt_read = try f.read(buf[0..]);
1209 current_line_start = 0;
1210 }
1211 }
1212 break :seek current_line_start;
1213 };
1214 const slice = buf[line_start..amt_read];
1215 if (mem.findScalar(u8, slice, '\n')) |pos| {
1216 const line = slice[0 .. pos + 1];
1217 mem.replaceScalar(u8, line, '\t', ' ');
1218 return writer.writeAll(line);
1219 } else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends.
1220 mem.replaceScalar(u8, slice, '\t', ' ');
1221 try writer.writeAll(slice);
1222 while (amt_read == buf.len) {
1223 amt_read = try f.read(buf[0..]);
1224 if (mem.findScalar(u8, buf[0..amt_read], '\n')) |pos| {
1225 const line = buf[0 .. pos + 1];
1226 mem.replaceScalar(u8, line, '\t', ' ');
1227 return writer.writeAll(line);
1228 } else {
1229 const line = buf[0..amt_read];
1230 mem.replaceScalar(u8, line, '\t', ' ');
1231 try writer.writeAll(line);
1232 }
1194 const cwd: Io.Dir = .cwd();
1195 var file = try cwd.openFile(io, source_location.file_name, .{});
1196 defer file.close(io);
1197
1198 var buffer: [4096]u8 = undefined;
1199 var file_reader: File.Reader = .init(file, io, &buffer);
1200 var line_index: usize = 0;
1201 const r = &file_reader.interface;
1202 while (true) {
1203 line_index += 1;
1204 if (line_index == source_location.line) {
1205 // TODO delete hard tabs from the language
1206 _ = try r.streamDelimiterEnding(writer, '\n');
1207 try writer.writeByte('\n');
1208 return;
12331209 }
1234 // Make sure printing last line of file inserts extra newline
1235 try writer.writeByte('\n');
1210 _ = try r.discardDelimiterInclusive('\n');
12361211 }
12371212}
12381213
12391214test printLineFromFile {
1240 var aw: Writer.Allocating = .init(std.testing.allocator);
1215 const io = testing.io;
1216 const gpa = testing.allocator;
1217
1218 var aw: Writer.Allocating = .init(gpa);
12411219 defer aw.deinit();
12421220 const output_stream = &aw.writer;
12431221
1244 const allocator = std.testing.allocator;
12451222 const join = std.fs.path.join;
1246 const expectError = std.testing.expectError;
1247 const expectEqualStrings = std.testing.expectEqualStrings;
1223 const expectError = testing.expectError;
1224 const expectEqualStrings = testing.expectEqualStrings;
12481225
1249 var test_dir = std.testing.tmpDir(.{});
1226 var test_dir = testing.tmpDir(.{});
12501227 defer test_dir.cleanup();
12511228 // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.
1252 const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
1253 defer allocator.free(test_dir_path);
1229 const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
1230 defer gpa.free(test_dir_path);
12541231
12551232 // Cases
12561233 {
1257 const path = try join(allocator, &.{ test_dir_path, "one_line.zig" });
1258 defer allocator.free(path);
1259 try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
1234 const path = try join(gpa, &.{ test_dir_path, "one_line.zig" });
1235 defer gpa.free(path);
1236 try test_dir.dir.writeFile(io, .{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
12601237
1261 try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
1238 try expectError(error.EndOfStream, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
12621239
1263 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1240 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
12641241 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
12651242 aw.clearRetainingCapacity();
12661243 }
12671244 {
1268 const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" });
1269 defer allocator.free(path);
1270 try test_dir.dir.writeFile(.{
1245 const path = try fs.path.join(gpa, &.{ test_dir_path, "three_lines.zig" });
1246 defer gpa.free(path);
1247 try test_dir.dir.writeFile(io, .{
12711248 .sub_path = "three_lines.zig",
12721249 .data =
12731250 \\1
......@@ -1276,90 +1253,90 @@ test printLineFromFile {
12761253 ,
12771254 });
12781255
1279 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1256 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
12801257 try expectEqualStrings("1\n", aw.written());
12811258 aw.clearRetainingCapacity();
12821259
1283 try printLineFromFile(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
1260 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 3, .column = 0 });
12841261 try expectEqualStrings("3\n", aw.written());
12851262 aw.clearRetainingCapacity();
12861263 }
12871264 {
1288 const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{});
1289 defer file.close();
1290 const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
1291 defer allocator.free(path);
1265 const file = try test_dir.dir.createFile(io, "line_overlaps_page_boundary.zig", .{});
1266 defer file.close(io);
1267 const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
1268 defer gpa.free(path);
12921269
12931270 const overlap = 10;
12941271 var buf: [16]u8 = undefined;
1295 var file_writer = file.writer(&buf);
1272 var file_writer = file.writer(io, &buf);
12961273 const writer = &file_writer.interface;
12971274 try writer.splatByteAll('a', std.heap.page_size_min - overlap);
12981275 try writer.writeByte('\n');
12991276 try writer.splatByteAll('a', overlap);
13001277 try writer.flush();
13011278
1302 try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
1279 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 });
13031280 try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
13041281 aw.clearRetainingCapacity();
13051282 }
13061283 {
1307 const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{});
1308 defer file.close();
1309 const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
1310 defer allocator.free(path);
1284 const file = try test_dir.dir.createFile(io, "file_ends_on_page_boundary.zig", .{});
1285 defer file.close(io);
1286 const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
1287 defer gpa.free(path);
13111288
1312 var file_writer = file.writer(&.{});
1289 var file_writer = file.writer(io, &.{});
13131290 const writer = &file_writer.interface;
13141291 try writer.splatByteAll('a', std.heap.page_size_max);
13151292
1316 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1293 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
13171294 try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
13181295 aw.clearRetainingCapacity();
13191296 }
13201297 {
1321 const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{});
1322 defer file.close();
1323 const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
1324 defer allocator.free(path);
1298 const file = try test_dir.dir.createFile(io, "very_long_first_line_spanning_multiple_pages.zig", .{});
1299 defer file.close(io);
1300 const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
1301 defer gpa.free(path);
13251302
1326 var file_writer = file.writer(&.{});
1303 var file_writer = file.writer(io, &.{});
13271304 const writer = &file_writer.interface;
13281305 try writer.splatByteAll('a', 3 * std.heap.page_size_max);
13291306
1330 try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
1307 try expectError(error.EndOfStream, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
13311308
1332 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1309 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
13331310 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
13341311 aw.clearRetainingCapacity();
13351312
13361313 try writer.writeAll("a\na");
13371314
1338 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1315 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
13391316 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
13401317 aw.clearRetainingCapacity();
13411318
1342 try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
1319 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 });
13431320 try expectEqualStrings("a\n", aw.written());
13441321 aw.clearRetainingCapacity();
13451322 }
13461323 {
1347 const file = try test_dir.dir.createFile("file_of_newlines.zig", .{});
1348 defer file.close();
1349 const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" });
1350 defer allocator.free(path);
1324 const file = try test_dir.dir.createFile(io, "file_of_newlines.zig", .{});
1325 defer file.close(io);
1326 const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" });
1327 defer gpa.free(path);
13511328
1352 var file_writer = file.writer(&.{});
1329 var file_writer = file.writer(io, &.{});
13531330 const writer = &file_writer.interface;
13541331 const real_file_start = 3 * std.heap.page_size_min;
13551332 try writer.splatByteAll('\n', real_file_start);
13561333 try writer.writeAll("abc\ndef");
13571334
1358 try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
1335 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
13591336 try expectEqualStrings("abc\n", aw.written());
13601337 aw.clearRetainingCapacity();
13611338
1362 try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
1339 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
13631340 try expectEqualStrings("def\n", aw.written());
13641341 aw.clearRetainingCapacity();
13651342 }
......@@ -1563,19 +1540,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
15631540 _ = panicking.fetchAdd(1, .seq_cst);
15641541
15651542 trace: {
1566 const stderr, const tty_config = lockStderrWriter(&.{});
1567 defer unlockStderrWriter();
1543 const stderr = lockStderr(&.{}).terminal();
1544 defer unlockStderr();
15681545
15691546 if (addr) |a| {
1570 stderr.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
1547 stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
15711548 } else {
1572 stderr.print("{s} (no address available)\n", .{name}) catch break :trace;
1549 stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace;
15731550 }
15741551 if (opt_ctx) |context| {
15751552 writeCurrentStackTrace(.{
15761553 .context = context,
15771554 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
1578 }, stderr, tty_config) catch break :trace;
1555 }, stderr) catch break :trace;
15791556 }
15801557 }
15811558 },
......@@ -1584,7 +1561,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
15841561 // A segfault happened while trying to print a previous panic message.
15851562 // We're still holding the mutex but that's fine as we're going to
15861563 // call abort().
1587 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
1564 const stderr = lockStderr(&.{}).terminal();
1565 stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
15881566 },
15891567 else => {}, // Panicked while printing the recursive panic message.
15901568 }
......@@ -1592,7 +1570,7 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
15921570 // We cannot allow the signal handler to return because when it runs the original instruction
15931571 // again, the memory may be mapped and undefined behavior would occur rather than repeating
15941572 // the segfault. So we simply abort here.
1595 posix.abort();
1573 std.process.abort();
15961574}
15971575
15981576pub fn dumpStackPointerAddr(prefix: []const u8) void {
......@@ -1616,20 +1594,14 @@ test "manage resources correctly" {
16161594 return @returnAddress();
16171595 }
16181596 };
1619 const gpa = std.testing.allocator;
1620 var threaded: Io.Threaded = .init_single_threaded;
1621 const io = threaded.ioBasic();
1622 var discarding: Io.Writer.Discarding = .init(&.{});
1597 const gpa = testing.allocator;
1598 const io = testing.io;
1599
1600 var discarding: Writer.Discarding = .init(&.{});
16231601 var di: SelfInfo = .init;
16241602 defer di.deinit(gpa);
1625 try printSourceAtAddress(
1626 gpa,
1627 io,
1628 &di,
1629 &discarding.writer,
1630 S.showMyTrace(),
1631 .no_color,
1632 );
1603 const t: Io.Terminal = .{ .writer = &discarding.writer, .mode = .no_color };
1604 try printSourceAtAddress(gpa, io, &di, t, S.showMyTrace());
16331605}
16341606
16351607/// This API helps you track where a value originated and where it was mutated,
......@@ -1690,21 +1662,21 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16901662 pub fn dump(t: @This()) void {
16911663 if (!enabled) return;
16921664
1693 const stderr, const tty_config = lockStderrWriter(&.{});
1694 defer unlockStderrWriter();
1665 const stderr = lockStderr(&.{}).terminal();
1666 defer unlockStderr();
16951667 const end = @min(t.index, size);
16961668 for (t.addrs[0..end], 0..) |frames_array, i| {
1697 stderr.print("{s}:\n", .{t.notes[i]}) catch return;
1669 stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return;
16981670 var frames_array_mutable = frames_array;
16991671 const frames = mem.sliceTo(frames_array_mutable[0..], 0);
17001672 const stack_trace: StackTrace = .{
17011673 .index = frames.len,
17021674 .instruction_addresses = frames,
17031675 };
1704 writeStackTrace(&stack_trace, stderr, tty_config) catch return;
1676 writeStackTrace(&stack_trace, stderr) catch return;
17051677 }
17061678 if (t.index > end) {
1707 stderr.print("{d} more traces not shown; consider increasing trace size\n", .{
1679 stderr.writer.print("{d} more traces not shown; consider increasing trace size\n", .{
17081680 t.index - end,
17091681 }) catch return;
17101682 }
lib/std/debug/ElfFile.zig+30-20
......@@ -1,5 +1,13 @@
11//! A helper type for loading an ELF file and collecting its DWARF debug information, unwind
22//! information, and symbol table.
3const ElfFile = @This();
4
5const std = @import("std");
6const Io = std.Io;
7const Endian = std.builtin.Endian;
8const Dwarf = std.debug.Dwarf;
9const Allocator = std.mem.Allocator;
10const elf = std.elf;
311
412is_64: bool,
513endian: Endian,
......@@ -115,7 +123,8 @@ pub const LoadError = error{
115123
116124pub fn load(
117125 gpa: Allocator,
118 elf_file: std.fs.File,
126 io: Io,
127 elf_file: Io.File,
119128 opt_build_id: ?[]const u8,
120129 di_search_paths: *const DebugInfoSearchPaths,
121130) LoadError!ElfFile {
......@@ -123,7 +132,7 @@ pub fn load(
123132 errdefer arena_instance.deinit();
124133 const arena = arena_instance.allocator();
125134
126 var result = loadInner(arena, elf_file, null) catch |err| switch (err) {
135 var result = loadInner(arena, io, elf_file, null) catch |err| switch (err) {
127136 error.CrcMismatch => unreachable, // we passed crc as null
128137 else => |e| return e,
129138 };
......@@ -148,7 +157,7 @@ pub fn load(
148157 if (build_id.len < 3) break :build_id;
149158
150159 for (di_search_paths.global_debug) |global_debug| {
151 if (try loadSeparateDebugFile(arena, &result, null, "{s}/.build-id/{x}/{x}.debug", .{
160 if (try loadSeparateDebugFile(arena, io, &result, null, "{s}/.build-id/{x}/{x}.debug", .{
152161 global_debug,
153162 build_id[0..1],
154163 build_id[1..],
......@@ -156,7 +165,7 @@ pub fn load(
156165 }
157166
158167 if (di_search_paths.debuginfod_client) |components| {
159 if (try loadSeparateDebugFile(arena, &result, null, "{s}{s}/{x}/debuginfo", .{
168 if (try loadSeparateDebugFile(arena, io, &result, null, "{s}{s}/{x}/debuginfo", .{
160169 components[0],
161170 components[1],
162171 build_id,
......@@ -173,18 +182,18 @@ pub fn load(
173182
174183 const exe_dir = di_search_paths.exe_dir orelse break :debug_link;
175184
176 if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}", .{
185 if (try loadSeparateDebugFile(arena, io, &result, debug_crc, "{s}/{s}", .{
177186 exe_dir,
178187 debug_filename,
179188 })) |mapped| break :load_di mapped;
180 if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/.debug/{s}", .{
189 if (try loadSeparateDebugFile(arena, io, &result, debug_crc, "{s}/.debug/{s}", .{
181190 exe_dir,
182191 debug_filename,
183192 })) |mapped| break :load_di mapped;
184193 for (di_search_paths.global_debug) |global_debug| {
185194 // This looks like a bug; it isn't. They really do embed the absolute path to the
186195 // exe's dirname, *under* the global debug path.
187 if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}/{s}", .{
196 if (try loadSeparateDebugFile(arena, io, &result, debug_crc, "{s}/{s}/{s}", .{
188197 global_debug,
189198 exe_dir,
190199 debug_filename,
......@@ -358,12 +367,19 @@ const Section = struct {
358367 const Array = std.enums.EnumArray(Section.Id, ?Section);
359368};
360369
361fn loadSeparateDebugFile(arena: Allocator, main_loaded: *LoadInnerResult, opt_crc: ?u32, comptime fmt: []const u8, args: anytype) Allocator.Error!?[]align(std.heap.page_size_min) const u8 {
370fn loadSeparateDebugFile(
371 arena: Allocator,
372 io: Io,
373 main_loaded: *LoadInnerResult,
374 opt_crc: ?u32,
375 comptime fmt: []const u8,
376 args: anytype,
377) Allocator.Error!?[]align(std.heap.page_size_min) const u8 {
362378 const path = try std.fmt.allocPrint(arena, fmt, args);
363 const elf_file = std.fs.cwd().openFile(path, .{}) catch return null;
364 defer elf_file.close();
379 const elf_file = Io.Dir.cwd().openFile(io, path, .{}) catch return null;
380 defer elf_file.close(io);
365381
366 const result = loadInner(arena, elf_file, opt_crc) catch |err| switch (err) {
382 const result = loadInner(arena, io, elf_file, opt_crc) catch |err| switch (err) {
367383 error.OutOfMemory => |e| return e,
368384 error.CrcMismatch => return null,
369385 else => return null,
......@@ -408,13 +424,14 @@ const LoadInnerResult = struct {
408424};
409425fn loadInner(
410426 arena: Allocator,
411 elf_file: std.fs.File,
427 io: Io,
428 elf_file: Io.File,
412429 opt_crc: ?u32,
413430) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult {
414431 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
415432 const file_len = std.math.cast(
416433 usize,
417 elf_file.getEndPos() catch |err| switch (err) {
434 elf_file.length(io) catch |err| switch (err) {
418435 error.PermissionDenied => unreachable, // not asking for PROT_EXEC
419436 else => |e| return e,
420437 },
......@@ -529,10 +546,3 @@ fn loadInner(
529546 .mapped_mem = mapped_mem,
530547 };
531548}
532
533const std = @import("std");
534const Endian = std.builtin.Endian;
535const Dwarf = std.debug.Dwarf;
536const ElfFile = @This();
537const Allocator = std.mem.Allocator;
538const elf = std.elf;
lib/std/debug/Info.zig+20-10
......@@ -5,19 +5,18 @@
55//! Unlike `std.debug.SelfInfo`, this API does not assume the debug information
66//! in question happens to match the host CPU architecture, OS, or other target
77//! properties.
8const Info = @This();
89
910const std = @import("../std.zig");
11const Io = std.Io;
1012const Allocator = std.mem.Allocator;
1113const Path = std.Build.Cache.Path;
1214const assert = std.debug.assert;
1315const Coverage = std.debug.Coverage;
1416const SourceLocation = std.debug.Coverage.SourceLocation;
15
1617const ElfFile = std.debug.ElfFile;
1718const MachOFile = std.debug.MachOFile;
1819
19const Info = @This();
20
2120impl: union(enum) {
2221 elf: ElfFile,
2322 macho: MachOFile,
......@@ -25,15 +24,25 @@ impl: union(enum) {
2524/// Externally managed, outlives this `Info` instance.
2625coverage: *Coverage,
2726
28pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError || error{ MissingDebugInfo, UnsupportedDebugInfo };
27pub const LoadError = error{
28 MissingDebugInfo,
29 UnsupportedDebugInfo,
30} || Io.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError;
2931
30pub fn load(gpa: Allocator, path: Path, coverage: *Coverage, format: std.Target.ObjectFormat, arch: std.Target.Cpu.Arch) LoadError!Info {
32pub fn load(
33 gpa: Allocator,
34 io: Io,
35 path: Path,
36 coverage: *Coverage,
37 format: std.Target.ObjectFormat,
38 arch: std.Target.Cpu.Arch,
39) LoadError!Info {
3140 switch (format) {
3241 .elf => {
33 var file = try path.root_dir.handle.openFile(path.sub_path, .{});
34 defer file.close();
42 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
43 defer file.close(io);
3544
36 var elf_file: ElfFile = try .load(gpa, file, null, &.none);
45 var elf_file: ElfFile = try .load(gpa, io, file, null, &.none);
3746 errdefer elf_file.deinit(gpa);
3847
3948 if (elf_file.dwarf == null) return error.MissingDebugInfo;
......@@ -49,7 +58,7 @@ pub fn load(gpa: Allocator, path: Path, coverage: *Coverage, format: std.Target.
4958 const path_str = try path.toString(gpa);
5059 defer gpa.free(path_str);
5160
52 var macho_file: MachOFile = try .load(gpa, path_str, arch);
61 var macho_file: MachOFile = try .load(gpa, io, path_str, arch);
5362 errdefer macho_file.deinit(gpa);
5463
5564 return .{
......@@ -76,6 +85,7 @@ pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError || error{U
7685pub fn resolveAddresses(
7786 info: *Info,
7887 gpa: Allocator,
88 io: Io,
7989 /// Asserts the addresses are in ascending order.
8090 sorted_pc_addrs: []const u64,
8191 /// Asserts its length equals length of `sorted_pc_addrs`.
......@@ -88,7 +98,7 @@ pub fn resolveAddresses(
8898 // Resolving all of the addresses at once unfortunately isn't so easy in Mach-O binaries
8999 // due to split debug information. For now, we'll just resolve the addreses one by one.
90100 for (sorted_pc_addrs, output) |pc_addr, *src_loc| {
91 const dwarf, const dwarf_pc_addr = mf.getDwarfForAddress(gpa, pc_addr) catch |err| switch (err) {
101 const dwarf, const dwarf_pc_addr = mf.getDwarfForAddress(gpa, io, pc_addr) catch |err| switch (err) {
92102 error.InvalidMachO, error.InvalidDwarf => return error.InvalidDebugInfo,
93103 else => |e| return e,
94104 };
lib/std/debug/MachOFile.zig+11-11
......@@ -27,13 +27,13 @@ pub fn deinit(mf: *MachOFile, gpa: Allocator) void {
2727 posix.munmap(mf.mapped_memory);
2828}
2929
30pub fn load(gpa: Allocator, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile {
30pub fn load(gpa: Allocator, io: Io, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile {
3131 switch (arch) {
3232 .x86_64, .aarch64 => {},
3333 else => unreachable,
3434 }
3535
36 const all_mapped_memory = try mapDebugInfoFile(path);
36 const all_mapped_memory = try mapDebugInfoFile(io, path);
3737 errdefer posix.munmap(all_mapped_memory);
3838
3939 // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal
......@@ -239,7 +239,7 @@ pub fn load(gpa: Allocator, path: []const u8, arch: std.Target.Cpu.Arch) Error!M
239239 .text_vmaddr = text_vmaddr,
240240 };
241241}
242pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, vaddr: u64) !struct { *Dwarf, u64 } {
242pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, io: Io, vaddr: u64) !struct { *Dwarf, u64 } {
243243 const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo;
244244
245245 if (symbol.ofile == Symbol.unknown_ofile) return error.MissingDebugInfo;
......@@ -254,7 +254,7 @@ pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, vaddr: u64) !struct {
254254 const gop = try mf.ofiles.getOrPut(gpa, symbol.ofile);
255255 if (!gop.found_existing) {
256256 const name = mem.sliceTo(mf.strings[symbol.ofile..], 0);
257 gop.value_ptr.* = loadOFile(gpa, name);
257 gop.value_ptr.* = loadOFile(gpa, io, name);
258258 }
259259 const of = &(gop.value_ptr.* catch |err| return err);
260260
......@@ -356,7 +356,7 @@ test {
356356 _ = Symbol;
357357}
358358
359fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {
359fn loadOFile(gpa: Allocator, io: Io, o_file_name: []const u8) !OFile {
360360 const all_mapped_memory, const mapped_ofile = map: {
361361 const open_paren = paren: {
362362 if (std.mem.endsWith(u8, o_file_name, ")")) {
......@@ -365,7 +365,7 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {
365365 }
366366 }
367367 // Not an archive, just a normal path to a .o file
368 const m = try mapDebugInfoFile(o_file_name);
368 const m = try mapDebugInfoFile(io, o_file_name);
369369 break :map .{ m, m };
370370 };
371371
......@@ -373,7 +373,7 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {
373373
374374 const archive_path = o_file_name[0..open_paren];
375375 const target_name_in_archive = o_file_name[open_paren + 1 .. o_file_name.len - 1];
376 const mapped_archive = try mapDebugInfoFile(archive_path);
376 const mapped_archive = try mapDebugInfoFile(io, archive_path);
377377 errdefer posix.munmap(mapped_archive);
378378
379379 var ar_reader: Io.Reader = .fixed(mapped_archive);
......@@ -511,16 +511,16 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {
511511}
512512
513513/// Uses `mmap` to map the file at `path` into memory.
514fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {
515 const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
514fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 {
515 const file = Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) {
516516 error.FileNotFound => return error.MissingDebugInfo,
517517 else => return error.ReadFailed,
518518 };
519 defer file.close();
519 defer file.close(io);
520520
521521 const file_len = std.math.cast(
522522 usize,
523 file.getEndPos() catch return error.ReadFailed,
523 file.length(io) catch return error.ReadFailed,
524524 ) orelse return error.ReadFailed;
525525
526526 return posix.mmap(
lib/std/debug/Pdb.zig+1-1
......@@ -1,5 +1,5 @@
11const std = @import("../std.zig");
2const File = std.fs.File;
2const File = std.Io.File;
33const Allocator = std.mem.Allocator;
44const pdb = std.pdb;
55const assert = std.debug.assert;
lib/std/debug/SelfInfo/Elf.zig+17-18
......@@ -29,13 +29,12 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
2929}
3030
3131pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {
32 _ = io;
3332 const module = try si.findModule(gpa, address, .exclusive);
3433 defer si.rwlock.unlock();
3534
3635 const vaddr = address - module.load_offset;
3736
38 const loaded_elf = try module.getLoadedElf(gpa);
37 const loaded_elf = try module.getLoadedElf(gpa, io);
3938 if (loaded_elf.file.dwarf) |*dwarf| {
4039 if (!loaded_elf.scanned_dwarf) {
4140 dwarf.open(gpa, native_endian) catch |err| switch (err) {
......@@ -180,7 +179,7 @@ comptime {
180179 }
181180}
182181pub const UnwindContext = Dwarf.SelfUnwinder;
183pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
182pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize {
184183 comptime assert(can_unwind);
185184
186185 {
......@@ -201,7 +200,7 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error
201200 @memset(si.unwind_cache.?, .empty);
202201 }
203202
204 const unwind_sections = try module.getUnwindSections(gpa);
203 const unwind_sections = try module.getUnwindSections(gpa, io);
205204 for (unwind_sections) |*unwind| {
206205 if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| {
207206 entry.populate(si.unwind_cache.?);
......@@ -261,12 +260,12 @@ const Module = struct {
261260 };
262261
263262 /// Assumes we already hold an exclusive lock.
264 fn getUnwindSections(mod: *Module, gpa: Allocator) Error![]Dwarf.Unwind {
265 if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa);
263 fn getUnwindSections(mod: *Module, gpa: Allocator, io: Io) Error![]Dwarf.Unwind {
264 if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa, io);
266265 const us = &(mod.unwind.? catch |err| return err);
267266 return us.buf[0..us.len];
268267 }
269 fn loadUnwindSections(mod: *Module, gpa: Allocator) Error!UnwindSections {
268 fn loadUnwindSections(mod: *Module, gpa: Allocator, io: Io) Error!UnwindSections {
270269 var us: UnwindSections = .{
271270 .buf = undefined,
272271 .len = 0,
......@@ -284,7 +283,7 @@ const Module = struct {
284283 } else {
285284 // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame`
286285 // section, but we'll have to load the binary to get at it.
287 const loaded = try mod.getLoadedElf(gpa);
286 const loaded = try mod.getLoadedElf(gpa, io);
288287 // If both are present, we can't just pick one -- the info could be split between them.
289288 // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one.
290289 if (loaded.file.debug_frame) |*debug_frame| {
......@@ -319,24 +318,24 @@ const Module = struct {
319318 }
320319
321320 /// Assumes we already hold an exclusive lock.
322 fn getLoadedElf(mod: *Module, gpa: Allocator) Error!*LoadedElf {
323 if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa);
321 fn getLoadedElf(mod: *Module, gpa: Allocator, io: Io) Error!*LoadedElf {
322 if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io);
324323 return if (mod.loaded_elf.?) |*elf| elf else |err| err;
325324 }
326 fn loadElf(mod: *Module, gpa: Allocator) Error!LoadedElf {
325 fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf {
327326 const load_result = if (mod.name.len > 0) res: {
328 var file = std.fs.cwd().openFile(mod.name, .{}) catch return error.MissingDebugInfo;
329 defer file.close();
330 break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name));
327 var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo;
328 defer file.close(io);
329 break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name));
331330 } else res: {
332 const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) {
331 const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) {
333332 error.OutOfMemory => |e| return e,
334333 else => return error.ReadFailed,
335334 };
336335 defer gpa.free(path);
337 var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo;
338 defer file.close();
339 break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path));
336 var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo;
337 defer file.close(io);
338 break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path));
340339 };
341340
342341 var elf_file = load_result catch |err| switch (err) {
lib/std/debug/SelfInfo/MachO.zig+10-10
......@@ -21,11 +21,10 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
2121}
2222
2323pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {
24 _ = io;
2524 const module = try si.findModule(gpa, address);
2625 defer si.mutex.unlock();
2726
28 const file = try module.getFile(gpa);
27 const file = try module.getFile(gpa, io);
2928
3029 // This is not necessarily the same as the vmaddr_slide that dyld would report. This is
3130 // because the segments in the file on disk might differ from the ones in memory. Normally
......@@ -39,7 +38,7 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st
3938
4039 const vaddr = address - vaddr_offset;
4140
42 const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, vaddr) catch {
41 const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, io, vaddr) catch {
4342 // Return at least the symbol name if available.
4443 return .{
4544 .name = try file.lookupSymbolName(vaddr),
......@@ -107,7 +106,8 @@ pub const UnwindContext = std.debug.Dwarf.SelfUnwinder;
107106/// Unwind a frame using MachO compact unwind info (from `__unwind_info`).
108107/// If the compact encoding can't encode a way to unwind a frame, it will
109108/// defer unwinding to DWARF, in which case `__eh_frame` will be used if available.
110pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
109pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize {
110 _ = io;
111111 return unwindFrameInner(si, gpa, context) catch |err| switch (err) {
112112 error.InvalidDebugInfo,
113113 error.MissingDebugInfo,
......@@ -546,12 +546,12 @@ const Module = struct {
546546 };
547547 }
548548
549 fn getFile(module: *Module, gpa: Allocator) Error!*MachOFile {
549 fn getFile(module: *Module, gpa: Allocator, io: Io) Error!*MachOFile {
550550 if (module.file == null) {
551551 const path = std.mem.span(
552552 std.c.dyld_image_path_containing_address(@ptrFromInt(module.text_base)).?,
553553 );
554 module.file = MachOFile.load(gpa, path, builtin.cpu.arch) catch |err| switch (err) {
554 module.file = MachOFile.load(gpa, io, path, builtin.cpu.arch) catch |err| switch (err) {
555555 error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo,
556556 error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e,
557557 };
......@@ -615,14 +615,14 @@ test {
615615}
616616
617617/// Uses `mmap` to map the file at `path` into memory.
618fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {
619 const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
618fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 {
619 const file = Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) {
620620 error.FileNotFound => return error.MissingDebugInfo,
621621 else => return error.ReadFailed,
622622 };
623 defer file.close();
623 defer file.close(io);
624624
625 const file_end_pos = file.getEndPos() catch |err| switch (err) {
625 const file_end_pos = file.length(io) catch |err| switch (err) {
626626 error.Unexpected => |e| return e,
627627 else => return error.ReadFailed,
628628 };
lib/std/debug/SelfInfo/Windows.zig+15-15
......@@ -149,8 +149,9 @@ pub const UnwindContext = struct {
149149 return ctx.cur.getRegs().bp;
150150 }
151151};
152pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
152pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize {
153153 _ = si;
154 _ = io;
154155 _ = gpa;
155156
156157 const current_regs = context.cur.getRegs();
......@@ -204,14 +205,14 @@ const Module = struct {
204205 coff_section_headers: []coff.SectionHeader,
205206
206207 const MappedFile = struct {
207 file: fs.File,
208 file: Io.File,
208209 section_handle: windows.HANDLE,
209210 section_view: []const u8,
210 fn deinit(mf: *const MappedFile) void {
211 fn deinit(mf: *const MappedFile, io: Io) void {
211212 const process_handle = windows.GetCurrentProcess();
212213 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mf.section_view.ptr)) == .SUCCESS);
213214 windows.CloseHandle(mf.section_handle);
214 mf.file.close();
215 mf.file.close(io);
215216 }
216217 };
217218
......@@ -222,7 +223,7 @@ const Module = struct {
222223 pdb.file_reader.file.close(io);
223224 pdb.deinit();
224225 }
225 if (di.mapped_file) |*mf| mf.deinit();
226 if (di.mapped_file) |*mf| mf.deinit(io);
226227
227228 var arena = di.arena.promote(gpa);
228229 arena.deinit();
......@@ -314,8 +315,8 @@ const Module = struct {
314315 );
315316 if (len == 0) return error.MissingDebugInfo;
316317 const name_w = name_buffer[0 .. len + 4 :0];
317 var threaded: Io.Threaded = .init_single_threaded;
318 const coff_file = threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
318 // TODO eliminate the reference to Io.Threaded.global_single_threaded here
319 const coff_file = Io.Threaded.global_single_threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
319320 error.Canceled => |e| return e,
320321 error.Unexpected => |e| return e,
321322 error.FileNotFound => return error.MissingDebugInfo,
......@@ -331,7 +332,6 @@ const Module = struct {
331332 error.SystemResources,
332333 error.WouldBlock,
333334 error.AccessDenied,
334 error.ProcessNotFound,
335335 error.PermissionDenied,
336336 error.NoSpaceLeft,
337337 error.DeviceBusy,
......@@ -343,7 +343,7 @@ const Module = struct {
343343 error.AntivirusInterference,
344344 error.ProcessFdQuotaExceeded,
345345 error.SystemFdQuotaExceeded,
346 error.FileLocksNotSupported,
346 error.FileLocksUnsupported,
347347 error.FileBusy,
348348 => return error.ReadFailed,
349349 };
......@@ -387,12 +387,12 @@ const Module = struct {
387387 const section_view = section_view_ptr.?[0..coff_len];
388388 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
389389 break :mapped .{
390 .file = .adaptFromNewApi(coff_file),
390 .file = coff_file,
391391 .section_handle = section_handle,
392392 .section_view = section_view,
393393 };
394394 };
395 errdefer if (mapped_file) |*mf| mf.deinit();
395 errdefer if (mapped_file) |*mf| mf.deinit(io);
396396
397397 const coff_image_base = coff_obj.getImageBase();
398398
......@@ -432,22 +432,22 @@ const Module = struct {
432432 break :pdb null;
433433 };
434434 const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: {
435 break :res std.fs.cwd().openFile(path, .{});
435 break :res Io.Dir.cwd().openFile(io, path, .{});
436436 } else res: {
437 const self_dir = fs.selfExeDirPathAlloc(gpa) catch |err| switch (err) {
437 const self_dir = std.process.executableDirPathAlloc(io, gpa) catch |err| switch (err) {
438438 error.OutOfMemory, error.Unexpected => |e| return e,
439439 else => return error.ReadFailed,
440440 };
441441 defer gpa.free(self_dir);
442442 const abs_path = try fs.path.join(gpa, &.{ self_dir, path });
443443 defer gpa.free(abs_path);
444 break :res std.fs.cwd().openFile(abs_path, .{});
444 break :res Io.Dir.cwd().openFile(io, abs_path, .{});
445445 };
446446 const pdb_file = pdb_file_open_result catch |err| switch (err) {
447447 error.FileNotFound, error.IsDir => break :pdb null,
448448 else => return error.ReadFailed,
449449 };
450 errdefer pdb_file.close();
450 errdefer pdb_file.close(io);
451451
452452 const pdb_reader = try arena.create(Io.File.Reader);
453453 pdb_reader.* = pdb_file.reader(io, try arena.alloc(u8, 4096));
lib/std/debug/simple_panic.zig+2-3
......@@ -14,9 +14,8 @@ const std = @import("../std.zig");
1414pub fn call(msg: []const u8, ra: ?usize) noreturn {
1515 @branchHint(.cold);
1616 _ = ra;
17 std.debug.lockStdErr();
18 const stderr: std.fs.File = .stderr();
19 stderr.writeAll(msg) catch {};
17 const stderr_writer = &std.debug.lockStderr(&.{}).file_writer.interface;
18 stderr_writer.writeAll(msg) catch {};
2019 @trap();
2120}
2221
lib/std/dynamic_library.zig+39-37
......@@ -1,10 +1,12 @@
1const std = @import("std.zig");
21const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3
4const std = @import("std.zig");
5const Io = std.Io;
36const mem = std.mem;
47const testing = std.testing;
58const elf = std.elf;
69const windows = std.os.windows;
7const native_os = builtin.os.tag;
810const posix = std.posix;
911
1012/// Cross-platform dynamic library loading and symbol lookup.
......@@ -53,11 +55,11 @@ pub const DynLib = struct {
5355// An iterator is provided in order to traverse the linked list in a idiomatic
5456// fashion.
5557const LinkMap = extern struct {
56 l_addr: usize,
57 l_name: [*:0]const u8,
58 l_ld: ?*elf.Dyn,
59 l_next: ?*LinkMap,
60 l_prev: ?*LinkMap,
58 addr: usize,
59 name: [*:0]const u8,
60 ld: ?*elf.Dyn,
61 next: ?*LinkMap,
62 prev: ?*LinkMap,
6163
6264 pub const Iterator = struct {
6365 current: ?*LinkMap,
......@@ -68,7 +70,7 @@ const LinkMap = extern struct {
6870
6971 pub fn next(self: *Iterator) ?*LinkMap {
7072 if (self.current) |it| {
71 self.current = it.l_next;
73 self.current = it.next;
7274 return it;
7375 }
7476 return null;
......@@ -77,10 +79,10 @@ const LinkMap = extern struct {
7779};
7880
7981const RDebug = extern struct {
80 r_version: i32,
81 r_map: ?*LinkMap,
82 r_brk: usize,
83 r_ldbase: usize,
82 version: i32,
83 map: ?*LinkMap,
84 brk: usize,
85 ldbase: usize,
8486};
8587
8688/// TODO fix comparisons of extern symbol pointers so we don't need this helper function.
......@@ -105,8 +107,8 @@ pub fn linkmap_iterator() error{InvalidExe}!LinkMap.Iterator {
105107 elf.DT_DEBUG => {
106108 const ptr = @as(?*RDebug, @ptrFromInt(_DYNAMIC[i].d_val));
107109 if (ptr) |r_debug| {
108 if (r_debug.r_version != 1) return error.InvalidExe;
109 break :init r_debug.r_map;
110 if (r_debug.version != 1) return error.InvalidExe;
111 break :init r_debug.map;
110112 }
111113 },
112114 elf.DT_PLTGOT => {
......@@ -155,24 +157,24 @@ pub const ElfDynLib = struct {
155157 dt_gnu_hash: *elf.gnu_hash.Header,
156158 };
157159
158 fn openPath(path: []const u8) !std.fs.Dir {
160 fn openPath(io: Io, path: []const u8) !Io.Dir {
159161 if (path.len == 0) return error.NotDir;
160162 var parts = std.mem.tokenizeScalar(u8, path, '/');
161 var parent = if (path[0] == '/') try std.fs.cwd().openDir("/", .{}) else std.fs.cwd();
163 var parent = if (path[0] == '/') try Io.Dir.cwd().openDir(io, "/", .{}) else Io.Dir.cwd();
162164 while (parts.next()) |part| {
163 const child = try parent.openDir(part, .{});
164 parent.close();
165 const child = try parent.openDir(io, part, .{});
166 parent.close(io);
165167 parent = child;
166168 }
167169 return parent;
168170 }
169171
170 fn resolveFromSearchPath(search_path: []const u8, file_name: []const u8, delim: u8) ?posix.fd_t {
172 fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?posix.fd_t {
171173 var paths = std.mem.tokenizeScalar(u8, search_path, delim);
172174 while (paths.next()) |p| {
173 var dir = openPath(p) catch continue;
174 defer dir.close();
175 const fd = posix.openat(dir.fd, file_name, .{
175 var dir = openPath(io, p) catch continue;
176 defer dir.close(io);
177 const fd = posix.openat(dir.handle, file_name, .{
176178 .ACCMODE = .RDONLY,
177179 .CLOEXEC = true,
178180 }, 0) catch continue;
......@@ -181,10 +183,10 @@ pub const ElfDynLib = struct {
181183 return null;
182184 }
183185
184 fn resolveFromParent(dir_path: []const u8, file_name: []const u8) ?posix.fd_t {
185 var dir = std.fs.cwd().openDir(dir_path, .{}) catch return null;
186 defer dir.close();
187 return posix.openat(dir.fd, file_name, .{
186 fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?posix.fd_t {
187 var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch return null;
188 defer dir.close(io);
189 return posix.openat(dir.handle, file_name, .{
188190 .ACCMODE = .RDONLY,
189191 .CLOEXEC = true,
190192 }, 0) catch null;
......@@ -195,7 +197,7 @@ pub const ElfDynLib = struct {
195197 // - DT_RPATH of the calling binary is not used as a search path
196198 // - DT_RUNPATH of the calling binary is not used as a search path
197199 // - /etc/ld.so.cache is not read
198 fn resolveFromName(path_or_name: []const u8) !posix.fd_t {
200 fn resolveFromName(io: Io, path_or_name: []const u8) !posix.fd_t {
199201 // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname
200202 if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| {
201203 return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
......@@ -206,25 +208,27 @@ pub const ElfDynLib = struct {
206208 std.os.linux.getegid() == std.os.linux.getgid())
207209 {
208210 if (posix.getenvZ("LD_LIBRARY_PATH")) |ld_library_path| {
209 if (resolveFromSearchPath(ld_library_path, path_or_name, ':')) |fd| {
211 if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |fd| {
210212 return fd;
211213 }
212214 }
213215 }
214216
215217 // Lastly the directories /lib and /usr/lib are searched (in this exact order)
216 if (resolveFromParent("/lib", path_or_name)) |fd| return fd;
217 if (resolveFromParent("/usr/lib", path_or_name)) |fd| return fd;
218 if (resolveFromParent(io, "/lib", path_or_name)) |fd| return fd;
219 if (resolveFromParent(io, "/usr/lib", path_or_name)) |fd| return fd;
218220 return error.FileNotFound;
219221 }
220222
221223 /// Trusts the file. Malicious file will be able to execute arbitrary code.
222224 pub fn open(path: []const u8) Error!ElfDynLib {
223 const fd = try resolveFromName(path);
225 const io = std.Options.debug_io;
226
227 const fd = try resolveFromName(io, path);
224228 defer posix.close(fd);
225229
226 const file: std.fs.File = .{ .handle = fd };
227 const stat = try file.stat();
230 const file: Io.File = .{ .handle = fd };
231 const stat = try file.stat(io);
228232 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
229233
230234 const page_size = std.heap.pageSize();
......@@ -549,11 +553,9 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: elf.Versym, vername: []const u8, str
549553}
550554
551555test "ElfDynLib" {
552 if (native_os != .linux) {
553 return error.SkipZigTest;
554 }
555
556 if (native_os != .linux) return error.SkipZigTest;
556557 try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so"));
558 try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so"));
557559}
558560
559561/// Separated to avoid referencing `WindowsDynLib`, because its field types may not
lib/std/fs.zig+7-555
......@@ -1,576 +1,28 @@
11//! File System.
2const builtin = @import("builtin");
3const native_os = builtin.os.tag;
42
53const std = @import("std.zig");
6const Io = std.Io;
7const root = @import("root");
8const mem = std.mem;
9const base64 = std.base64;
10const crypto = std.crypto;
11const Allocator = std.mem.Allocator;
12const assert = std.debug.assert;
13const posix = std.posix;
14const windows = std.os.windows;
154
16const is_darwin = native_os.isDarwin();
17
18pub const AtomicFile = @import("fs/AtomicFile.zig");
19pub const Dir = @import("fs/Dir.zig");
20pub const File = @import("fs/File.zig");
5/// Deprecated, use `std.Io.Dir.path`.
216pub const path = @import("fs/path.zig");
22
23pub const has_executable_bit = switch (native_os) {
24 .windows, .wasi => false,
25 else => true,
26};
27
287pub const wasi = @import("fs/wasi.zig");
298
30// TODO audit these APIs with respect to Dir and absolute paths
31
32pub const realpath = posix.realpath;
33pub const realpathZ = posix.realpathZ;
34pub const realpathW = posix.realpathW;
35pub const realpathW2 = posix.realpathW2;
36
379pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
3810pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
3911
40/// The maximum length of a file path that the operating system will accept.
41///
42/// Paths, including those returned from file system operations, may be longer
43/// than this length, but such paths cannot be successfully passed back in
44/// other file system operations. However, all path components returned by file
45/// system operations are assumed to fit into a `u8` array of this length.
46///
47/// The byte count includes room for a null sentinel byte.
48///
49/// * On Windows, `[]u8` file paths are encoded as
50/// [WTF-8](https://wtf-8.codeberg.page/).
51/// * On WASI, `[]u8` file paths are encoded as valid UTF-8.
52/// * On other platforms, `[]u8` file paths are opaque sequences of bytes with
53/// no particular encoding.
54pub const max_path_bytes = switch (native_os) {
55 .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .illumos, .plan9, .emscripten, .wasi, .serenity => posix.PATH_MAX,
56 // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes.
57 // If it would require 4 WTF-8 bytes, then there would be a surrogate
58 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
59 // +1 for the null byte at the end, which can be encoded in 1 byte.
60 .windows => windows.PATH_MAX_WIDE * 3 + 1,
61 else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX"))
62 root.os.PATH_MAX
63 else
64 @compileError("PATH_MAX not implemented for " ++ @tagName(native_os)),
65};
66
67/// This represents the maximum size of a `[]u8` file name component that
68/// the platform's common file systems support. File name components returned by file system
69/// operations are likely to fit into a `u8` array of this length, but
70/// (depending on the platform) this assumption may not hold for every configuration.
71/// The byte count does not include a null sentinel byte.
72/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://wtf-8.codeberg.page/).
73/// On WASI, file name components are encoded as valid UTF-8.
74/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding.
75pub const max_name_bytes = switch (native_os) {
76 .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .illumos, .serenity => posix.NAME_MAX,
77 // Haiku's NAME_MAX includes the null terminator, so subtract one.
78 .haiku => posix.NAME_MAX - 1,
79 // Each WTF-16LE character may be expanded to 3 WTF-8 bytes.
80 // If it would require 4 WTF-8 bytes, then there would be a surrogate
81 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
82 .windows => windows.NAME_MAX * 3,
83 // For WASI, the MAX_NAME will depend on the host OS, so it needs to be
84 // as large as the largest max_name_bytes (Windows) in order to work on any host OS.
85 // TODO determine if this is a reasonable approach
86 .wasi => windows.NAME_MAX * 3,
87 else => if (@hasDecl(root, "os") and @hasDecl(root.os, "NAME_MAX"))
88 root.os.NAME_MAX
89 else
90 @compileError("NAME_MAX not implemented for " ++ @tagName(native_os)),
91};
92
9312pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*;
9413
9514/// Base64 encoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
96pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);
15pub const base64_encoder = std.base64.Base64Encoder.init(base64_alphabet, null);
9716
9817/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
99pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
100
101/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
102/// are absolute. See `Dir.copyFile` for a function that operates on both
103/// absolute and relative paths.
104/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
105/// On WASI, both paths should be encoded as valid UTF-8.
106/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
107pub fn copyFileAbsolute(
108 source_path: []const u8,
109 dest_path: []const u8,
110 args: Dir.CopyFileOptions,
111) !void {
112 assert(path.isAbsolute(source_path));
113 assert(path.isAbsolute(dest_path));
114 const my_cwd = cwd();
115 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
116}
117
118test copyFileAbsolute {}
119
120/// Create a new directory, based on an absolute path.
121/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
122/// on both absolute and relative paths.
123/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
124/// On WASI, `absolute_path` should be encoded as valid UTF-8.
125/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
126pub fn makeDirAbsolute(absolute_path: []const u8) !void {
127 assert(path.isAbsolute(absolute_path));
128 return posix.mkdir(absolute_path, Dir.default_mode);
129}
130
131test makeDirAbsolute {}
132
133/// Same as `makeDirAbsolute` except the parameter is null-terminated.
134pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
135 assert(path.isAbsoluteZ(absolute_path_z));
136 return posix.mkdirZ(absolute_path_z, Dir.default_mode);
137}
138
139test makeDirAbsoluteZ {}
140
141/// Same as `Dir.deleteDir` except the path is absolute.
142/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
143/// On WASI, `dir_path` should be encoded as valid UTF-8.
144/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
145pub fn deleteDirAbsolute(dir_path: []const u8) !void {
146 assert(path.isAbsolute(dir_path));
147 return posix.rmdir(dir_path);
148}
149
150/// Same as `deleteDirAbsolute` except the path parameter is null-terminated.
151pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {
152 assert(path.isAbsoluteZ(dir_path));
153 return posix.rmdirZ(dir_path);
154}
155
156/// Same as `Dir.rename` except the paths are absolute.
157/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
158/// On WASI, both paths should be encoded as valid UTF-8.
159/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
160pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
161 assert(path.isAbsolute(old_path));
162 assert(path.isAbsolute(new_path));
163 return posix.rename(old_path, new_path);
164}
165
166/// Same as `renameAbsolute` except the path parameters are null-terminated.
167pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
168 assert(path.isAbsoluteZ(old_path));
169 assert(path.isAbsoluteZ(new_path));
170 return posix.renameZ(old_path, new_path);
171}
172
173/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
174pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
175 return posix.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
176}
177
178/// Same as `rename` except the parameters are null-terminated.
179pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void {
180 return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
181}
182
183/// Deprecated in favor of `Io.Dir.cwd`.
184pub fn cwd() Dir {
185 if (native_os == .windows) {
186 return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle };
187 } else if (native_os == .wasi) {
188 return .{ .fd = std.options.wasiCwd() };
189 } else {
190 return .{ .fd = posix.AT.FDCWD };
191 }
192}
193
194pub fn defaultWasiCwd() std.os.wasi.fd_t {
195 // Expect the first preopen to be current working directory.
196 return 3;
197}
198
199/// Opens a directory at the given path. The directory is a system resource that remains
200/// open until `close` is called on the result.
201/// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.
202///
203/// Asserts that the path parameter has no null bytes.
204/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
205/// On WASI, `absolute_path` should be encoded as valid UTF-8.
206/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
207pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenOptions) File.OpenError!Dir {
208 assert(path.isAbsolute(absolute_path));
209 return cwd().openDir(absolute_path, flags);
210}
211
212/// Same as `openDirAbsolute` but the path parameter is null-terminated.
213pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenOptions) File.OpenError!Dir {
214 assert(path.isAbsoluteZ(absolute_path_c));
215 return cwd().openDirZ(absolute_path_c, flags);
216}
217/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.
218/// Call `File.close` to release the resource.
219/// Asserts that the path is absolute. See `Dir.openFile` for a function that
220/// operates on both absolute and relative paths.
221/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function
222/// that accepts a null-terminated path.
223/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
224/// On WASI, `absolute_path` should be encoded as valid UTF-8.
225/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
226pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
227 assert(path.isAbsolute(absolute_path));
228 return cwd().openFile(absolute_path, flags);
229}
230
231/// Test accessing `path`.
232/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
233/// For example, instead of testing if a file exists and then opening it, just
234/// open it and handle the error for file not found.
235/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
236/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
237/// On WASI, `absolute_path` should be encoded as valid UTF-8.
238/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
239pub fn accessAbsolute(absolute_path: []const u8, flags: Io.Dir.AccessOptions) Dir.AccessError!void {
240 assert(path.isAbsolute(absolute_path));
241 try cwd().access(absolute_path, flags);
242}
243/// Creates, opens, or overwrites a file with write access, based on an absolute path.
244/// Call `File.close` to release the resource.
245/// Asserts that the path is absolute. See `Dir.createFile` for a function that
246/// operates on both absolute and relative paths.
247/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function
248/// that accepts a null-terminated path.
249/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
250/// On WASI, `absolute_path` should be encoded as valid UTF-8.
251/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
252pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
253 assert(path.isAbsolute(absolute_path));
254 return cwd().createFile(absolute_path, flags);
255}
256
257/// Delete a file name and possibly the file it refers to, based on an absolute path.
258/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
259/// operates on both absolute and relative paths.
260/// Asserts that the path parameter has no null bytes.
261/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
262/// On WASI, `absolute_path` should be encoded as valid UTF-8.
263/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
264pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
265 assert(path.isAbsolute(absolute_path));
266 return cwd().deleteFile(absolute_path);
267}
268
269/// Removes a symlink, file, or directory.
270/// This is equivalent to `Dir.deleteTree` with the base directory.
271/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
272/// operates on both absolute and relative paths.
273/// Asserts that the path parameter has no null bytes.
274/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
275/// On WASI, `absolute_path` should be encoded as valid UTF-8.
276/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
277pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
278 assert(path.isAbsolute(absolute_path));
279 const dirname = path.dirname(absolute_path) orelse return error{
280 /// Attempt to remove the root file system path.
281 /// This error is unreachable if `absolute_path` is relative.
282 CannotDeleteRootDirectory,
283 }.CannotDeleteRootDirectory;
284
285 var dir = try cwd().openDir(dirname, .{});
286 defer dir.close();
287
288 return dir.deleteTree(path.basename(absolute_path));
289}
290
291/// Same as `Dir.readLink`, except it asserts the path is absolute.
292/// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
293/// On WASI, `pathname` should be encoded as valid UTF-8.
294/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
295pub fn readLinkAbsolute(pathname: []const u8, buffer: *[max_path_bytes]u8) ![]u8 {
296 assert(path.isAbsolute(pathname));
297 return posix.readlink(pathname, buffer);
298}
299
300/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
301/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
302/// one; the latter case is known as a dangling link.
303/// If `sym_link_path` exists, it will not be overwritten.
304/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
305/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
306/// On WASI, both paths should be encoded as valid UTF-8.
307/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
308pub fn symLinkAbsolute(
309 target_path: []const u8,
310 sym_link_path: []const u8,
311 flags: Dir.SymLinkFlags,
312) !void {
313 assert(path.isAbsolute(target_path));
314 assert(path.isAbsolute(sym_link_path));
315 if (native_os == .windows) {
316 const target_path_w = try windows.sliceToPrefixedFileW(null, target_path);
317 const sym_link_path_w = try windows.sliceToPrefixedFileW(null, sym_link_path);
318 return windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
319 }
320 return posix.symlink(target_path, sym_link_path);
321}
322
323/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 LE encoded.
324/// Note that this function will by default try creating a symbolic link to a file. If you would
325/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
326/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
327pub fn symLinkAbsoluteW(
328 target_path_w: [*:0]const u16,
329 sym_link_path_w: [*:0]const u16,
330 flags: Dir.SymLinkFlags,
331) !void {
332 assert(path.isAbsoluteWindowsW(target_path_w));
333 assert(path.isAbsoluteWindowsW(sym_link_path_w));
334 return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory);
335}
336
337pub const OpenSelfExeError = Io.File.OpenSelfExeError;
338
339/// Deprecated in favor of `Io.File.openSelfExe`.
340pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
341 if (native_os == .linux or native_os == .serenity or native_os == .windows) {
342 var threaded: Io.Threaded = .init_single_threaded;
343 const io = threaded.ioBasic();
344 return .adaptFromNewApi(try Io.File.openSelfExe(io, flags));
345 }
346 // Use of max_path_bytes here is valid as the resulting path is immediately
347 // opened with no modification.
348 var buf: [max_path_bytes]u8 = undefined;
349 const self_exe_path = try selfExePath(&buf);
350 buf[self_exe_path.len] = 0;
351 return openFileAbsolute(buf[0..self_exe_path.len :0], flags);
352}
353
354// This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded
355pub const SelfExePathError = error{
356 FileNotFound,
357 AccessDenied,
358 NameTooLong,
359 NotSupported,
360 NotDir,
361 SymLinkLoop,
362 InputOutput,
363 FileTooBig,
364 IsDir,
365 ProcessFdQuotaExceeded,
366 SystemFdQuotaExceeded,
367 NoDevice,
368 SystemResources,
369 NoSpaceLeft,
370 FileSystem,
371 BadPathName,
372 DeviceBusy,
373 SharingViolation,
374 PipeBusy,
375 NotLink,
376 PathAlreadyExists,
377
378 /// On Windows, `\\server` or `\\server\share` was not found.
379 NetworkNotFound,
380 ProcessNotFound,
18pub const base64_decoder = std.base64.Base64Decoder.init(base64_alphabet, null);
38119
382 /// On Windows, antivirus software is enabled by default. It can be
383 /// disabled, but Windows Update sometimes ignores the user's preference
384 /// and re-enables it. When enabled, antivirus software on Windows
385 /// intercepts file system operations and makes them significantly slower
386 /// in addition to possibly failing with this error code.
387 AntivirusInterference,
388
389 /// On Windows, the volume does not contain a recognized file system. File
390 /// system drivers might not be loaded, or the volume may be corrupt.
391 UnrecognizedVolume,
392
393 Canceled,
394} || posix.SysCtlError;
395
396/// `selfExePath` except allocates the result on the heap.
397/// Caller owns returned memory.
398pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
399 // Use of max_path_bytes here is justified as, at least on one tested Linux
400 // system, readlink will completely fail to return a result larger than
401 // PATH_MAX even if given a sufficiently large buffer. This makes it
402 // fundamentally impossible to get the selfExePath of a program running in
403 // a very deeply nested directory chain in this way.
404 // TODO(#4812): Investigate other systems and whether it is possible to get
405 // this path by trying larger and larger buffers until one succeeds.
406 var buf: [max_path_bytes]u8 = undefined;
407 return allocator.dupe(u8, try selfExePath(&buf));
408}
409
410/// Get the path to the current executable. Follows symlinks.
411/// If you only need the directory, use selfExeDirPath.
412/// If you only want an open file handle, use openSelfExe.
413/// This function may return an error if the current executable
414/// was deleted after spawning.
415/// Returned value is a slice of out_buffer.
416/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
417/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
418///
419/// On Linux, depends on procfs being mounted. If the currently executing binary has
420/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
421/// TODO make the return type of this a null terminated pointer
422pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
423 if (is_darwin) {
424 // Note that _NSGetExecutablePath() will return "a path" to
425 // the executable not a "real path" to the executable.
426 var symlink_path_buf: [max_path_bytes:0]u8 = undefined;
427 var u32_len: u32 = max_path_bytes + 1; // include the sentinel
428 const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len);
429 if (rc != 0) return error.NameTooLong;
430
431 var real_path_buf: [max_path_bytes]u8 = undefined;
432 const real_path = std.posix.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) {
433 error.NetworkNotFound => unreachable, // Windows-only
434 else => |e| return e,
435 };
436 if (real_path.len > out_buffer.len) return error.NameTooLong;
437 const result = out_buffer[0..real_path.len];
438 @memcpy(result, real_path);
439 return result;
440 }
441 switch (native_os) {
442 .linux, .serenity => return posix.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) {
443 error.UnsupportedReparsePointType => unreachable, // Windows-only
444 error.NetworkNotFound => unreachable, // Windows-only
445 else => |e| return e,
446 },
447 .illumos => return posix.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) {
448 error.UnsupportedReparsePointType => unreachable, // Windows-only
449 error.NetworkNotFound => unreachable, // Windows-only
450 else => |e| return e,
451 },
452 .freebsd, .dragonfly => {
453 var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 };
454 var out_len: usize = out_buffer.len;
455 try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0);
456 // TODO could this slice from 0 to out_len instead?
457 return mem.sliceTo(out_buffer, 0);
458 },
459 .netbsd => {
460 var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME };
461 var out_len: usize = out_buffer.len;
462 try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0);
463 // TODO could this slice from 0 to out_len instead?
464 return mem.sliceTo(out_buffer, 0);
465 },
466 .openbsd, .haiku => {
467 // OpenBSD doesn't support getting the path of a running process, so try to guess it
468 if (std.os.argv.len == 0)
469 return error.FileNotFound;
470
471 const argv0 = mem.span(std.os.argv[0]);
472 if (mem.find(u8, argv0, "/") != null) {
473 // argv[0] is a path (relative or absolute): use realpath(3) directly
474 var real_path_buf: [max_path_bytes]u8 = undefined;
475 const real_path = posix.realpathZ(std.os.argv[0], &real_path_buf) catch |err| switch (err) {
476 error.NetworkNotFound => unreachable, // Windows-only
477 else => |e| return e,
478 };
479 if (real_path.len > out_buffer.len)
480 return error.NameTooLong;
481 const result = out_buffer[0..real_path.len];
482 @memcpy(result, real_path);
483 return result;
484 } else if (argv0.len != 0) {
485 // argv[0] is not empty (and not a path): search it inside PATH
486 const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound;
487 var path_it = mem.tokenizeScalar(u8, PATH, path.delimiter);
488 while (path_it.next()) |a_path| {
489 var resolved_path_buf: [max_path_bytes - 1:0]u8 = undefined;
490 const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{
491 a_path,
492 std.os.argv[0],
493 }, 0) catch continue;
494
495 var real_path_buf: [max_path_bytes]u8 = undefined;
496 if (posix.realpathZ(resolved_path, &real_path_buf)) |real_path| {
497 // found a file, and hope it is the right file
498 if (real_path.len > out_buffer.len)
499 return error.NameTooLong;
500 const result = out_buffer[0..real_path.len];
501 @memcpy(result, real_path);
502 return result;
503 } else |_| continue;
504 }
505 }
506 return error.FileNotFound;
507 },
508 .windows => {
509 const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
510 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
511
512 // If ImagePathName is a symlink, then it will contain the path of the
513 // symlink, not the path that the symlink points to. We want the path
514 // that the symlink points to, though, so we need to get the realpath.
515 var pathname_w = try windows.wToPrefixedFileW(null, image_path_name);
516
517 const wide_slice = try std.fs.cwd().realpathW2(pathname_w.span(), &pathname_w.data);
518
519 const len = std.unicode.calcWtf8Len(wide_slice);
520 if (len > out_buffer.len)
521 return error.NameTooLong;
522
523 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
524 return out_buffer[0..end_index];
525 },
526 else => @compileError("std.fs.selfExePath not supported for this target"),
527 }
528}
529
530/// `selfExeDirPath` except allocates the result on the heap.
531/// Caller owns returned memory.
532pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 {
533 // Use of max_path_bytes here is justified as, at least on one tested Linux
534 // system, readlink will completely fail to return a result larger than
535 // PATH_MAX even if given a sufficiently large buffer. This makes it
536 // fundamentally impossible to get the selfExeDirPath of a program running
537 // in a very deeply nested directory chain in this way.
538 // TODO(#4812): Investigate other systems and whether it is possible to get
539 // this path by trying larger and larger buffers until one succeeds.
540 var buf: [max_path_bytes]u8 = undefined;
541 return allocator.dupe(u8, try selfExeDirPath(&buf));
542}
543
544/// Get the directory path that contains the current executable.
545/// Returned value is a slice of out_buffer.
546/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
547/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
548pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
549 const self_exe_path = try selfExePath(out_buffer);
550 // Assume that the OS APIs return absolute paths, and therefore dirname
551 // will not return null.
552 return path.dirname(self_exe_path).?;
553}
554
555/// `realpath`, except caller must free the returned memory.
556/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
557/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
558/// See also `Dir.realpath`.
559pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
560 // Use of max_path_bytes here is valid as the realpath function does not
561 // have a variant that takes an arbitrary-size buffer.
562 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
563 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
564 // paths. musl supports passing NULL but restricts the output to PATH_MAX
565 // anyway.
566 var buf: [max_path_bytes]u8 = undefined;
567 return allocator.dupe(u8, try posix.realpath(pathname, &buf));
568}
20/// Deprecated, use `std.Io.Dir.max_path_bytes`.
21pub const max_path_bytes = std.Io.Dir.max_path_bytes;
22/// Deprecated, use `std.Io.Dir.max_name_bytes`.
23pub const max_name_bytes = std.Io.Dir.max_name_bytes;
56924
57025test {
571 _ = AtomicFile;
572 _ = Dir;
573 _ = File;
57426 _ = path;
57527 _ = @import("fs/test.zig");
57628 _ = @import("fs/get_app_data_dir.zig");
lib/std/fs/AtomicFile.zig deleted-94
......@@ -1,94 +0,0 @@
1const AtomicFile = @This();
2const std = @import("../std.zig");
3const File = std.fs.File;
4const Dir = std.fs.Dir;
5const fs = std.fs;
6const assert = std.debug.assert;
7const posix = std.posix;
8
9file_writer: File.Writer,
10random_integer: u64,
11dest_basename: []const u8,
12file_open: bool,
13file_exists: bool,
14close_dir_on_deinit: bool,
15dir: Dir,
16
17pub const InitError = File.OpenError;
18
19/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
20pub fn init(
21 dest_basename: []const u8,
22 mode: File.Mode,
23 dir: Dir,
24 close_dir_on_deinit: bool,
25 write_buffer: []u8,
26) InitError!AtomicFile {
27 while (true) {
28 const random_integer = std.crypto.random.int(u64);
29 const tmp_sub_path = std.fmt.hex(random_integer);
30 const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) {
31 error.PathAlreadyExists => continue,
32 else => |e| return e,
33 };
34 return .{
35 .file_writer = file.writer(write_buffer),
36 .random_integer = random_integer,
37 .dest_basename = dest_basename,
38 .file_open = true,
39 .file_exists = true,
40 .close_dir_on_deinit = close_dir_on_deinit,
41 .dir = dir,
42 };
43 }
44}
45
46/// Always call deinit, even after a successful finish().
47pub fn deinit(af: *AtomicFile) void {
48 if (af.file_open) {
49 af.file_writer.file.close();
50 af.file_open = false;
51 }
52 if (af.file_exists) {
53 const tmp_sub_path = std.fmt.hex(af.random_integer);
54 af.dir.deleteFile(&tmp_sub_path) catch {};
55 af.file_exists = false;
56 }
57 if (af.close_dir_on_deinit) {
58 af.dir.close();
59 }
60 af.* = undefined;
61}
62
63pub const FlushError = File.WriteError;
64
65pub fn flush(af: *AtomicFile) FlushError!void {
66 af.file_writer.interface.flush() catch |err| switch (err) {
67 error.WriteFailed => return af.file_writer.err.?,
68 };
69}
70
71pub const RenameIntoPlaceError = posix.RenameError;
72
73/// On Windows, this function introduces a period of time where some file
74/// system operations on the destination file will result in
75/// `error.AccessDenied`, including rename operations (such as the one used in
76/// this function).
77pub fn renameIntoPlace(af: *AtomicFile) RenameIntoPlaceError!void {
78 assert(af.file_exists);
79 if (af.file_open) {
80 af.file_writer.file.close();
81 af.file_open = false;
82 }
83 const tmp_sub_path = std.fmt.hex(af.random_integer);
84 try posix.renameat(af.dir.fd, &tmp_sub_path, af.dir.fd, af.dest_basename);
85 af.file_exists = false;
86}
87
88pub const FinishError = FlushError || RenameIntoPlaceError;
89
90/// Combination of `flush` followed by `renameIntoPlace`.
91pub fn finish(af: *AtomicFile) FinishError!void {
92 try af.flush();
93 try af.renameIntoPlace();
94}
lib/std/fs/Dir.zig deleted-2065
......@@ -1,2065 +0,0 @@
1//! Deprecated in favor of `Io.Dir`.
2const Dir = @This();
3
4const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6
7const std = @import("../std.zig");
8const Io = std.Io;
9const File = std.fs.File;
10const AtomicFile = std.fs.AtomicFile;
11const base64_encoder = fs.base64_encoder;
12const posix = std.posix;
13const mem = std.mem;
14const path = fs.path;
15const fs = std.fs;
16const Allocator = std.mem.Allocator;
17const assert = std.debug.assert;
18const linux = std.os.linux;
19const windows = std.os.windows;
20const have_flock = @TypeOf(posix.system.flock) != void;
21
22fd: Handle,
23
24pub const Handle = posix.fd_t;
25
26pub const default_mode = 0o755;
27
28pub const Entry = struct {
29 name: []const u8,
30 kind: Kind,
31
32 pub const Kind = File.Kind;
33};
34
35const IteratorError = error{
36 AccessDenied,
37 PermissionDenied,
38 SystemResources,
39} || posix.UnexpectedError;
40
41pub const Iterator = switch (native_os) {
42 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => struct {
43 dir: Dir,
44 seek: i64,
45 buf: [1024]u8 align(@alignOf(posix.system.dirent)),
46 index: usize,
47 end_index: usize,
48 first_iter: bool,
49
50 const Self = @This();
51
52 pub const Error = IteratorError;
53
54 /// Memory such as file names referenced in this returned entry becomes invalid
55 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
56 pub fn next(self: *Self) Error!?Entry {
57 switch (native_os) {
58 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => return self.nextDarwin(),
59 .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
60 .illumos => return self.nextIllumos(),
61 else => @compileError("unimplemented"),
62 }
63 }
64
65 fn nextDarwin(self: *Self) !?Entry {
66 start_over: while (true) {
67 if (self.index >= self.end_index) {
68 if (self.first_iter) {
69 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
70 self.first_iter = false;
71 }
72 const rc = posix.system.getdirentries(
73 self.dir.fd,
74 &self.buf,
75 self.buf.len,
76 &self.seek,
77 );
78 if (rc == 0) return null;
79 if (rc < 0) {
80 switch (posix.errno(rc)) {
81 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
82 .FAULT => unreachable,
83 .NOTDIR => unreachable,
84 .INVAL => unreachable,
85 else => |err| return posix.unexpectedErrno(err),
86 }
87 }
88 self.index = 0;
89 self.end_index = @as(usize, @intCast(rc));
90 }
91 const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
92 const next_index = self.index + darwin_entry.reclen;
93 self.index = next_index;
94
95 const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen];
96
97 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) {
98 continue :start_over;
99 }
100
101 const entry_kind: Entry.Kind = switch (darwin_entry.type) {
102 posix.DT.BLK => .block_device,
103 posix.DT.CHR => .character_device,
104 posix.DT.DIR => .directory,
105 posix.DT.FIFO => .named_pipe,
106 posix.DT.LNK => .sym_link,
107 posix.DT.REG => .file,
108 posix.DT.SOCK => .unix_domain_socket,
109 posix.DT.WHT => .whiteout,
110 else => .unknown,
111 };
112 return Entry{
113 .name = name,
114 .kind = entry_kind,
115 };
116 }
117 }
118
119 fn nextIllumos(self: *Self) !?Entry {
120 start_over: while (true) {
121 if (self.index >= self.end_index) {
122 if (self.first_iter) {
123 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
124 self.first_iter = false;
125 }
126 const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
127 switch (posix.errno(rc)) {
128 .SUCCESS => {},
129 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
130 .FAULT => unreachable,
131 .NOTDIR => unreachable,
132 .INVAL => unreachable,
133 else => |err| return posix.unexpectedErrno(err),
134 }
135 if (rc == 0) return null;
136 self.index = 0;
137 self.end_index = @as(usize, @intCast(rc));
138 }
139 const entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
140 const next_index = self.index + entry.reclen;
141 self.index = next_index;
142
143 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
144 if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
145 continue :start_over;
146
147 // illumos dirent doesn't expose type, so we have to call stat to get it.
148 const stat_info = posix.fstatat(
149 self.dir.fd,
150 name,
151 posix.AT.SYMLINK_NOFOLLOW,
152 ) catch |err| switch (err) {
153 error.NameTooLong => unreachable,
154 error.SymLinkLoop => unreachable,
155 error.FileNotFound => unreachable, // lost the race
156 else => |e| return e,
157 };
158 const entry_kind: Entry.Kind = switch (stat_info.mode & posix.S.IFMT) {
159 posix.S.IFIFO => .named_pipe,
160 posix.S.IFCHR => .character_device,
161 posix.S.IFDIR => .directory,
162 posix.S.IFBLK => .block_device,
163 posix.S.IFREG => .file,
164 posix.S.IFLNK => .sym_link,
165 posix.S.IFSOCK => .unix_domain_socket,
166 posix.S.IFDOOR => .door,
167 posix.S.IFPORT => .event_port,
168 else => .unknown,
169 };
170 return Entry{
171 .name = name,
172 .kind = entry_kind,
173 };
174 }
175 }
176
177 fn nextBsd(self: *Self) !?Entry {
178 start_over: while (true) {
179 if (self.index >= self.end_index) {
180 if (self.first_iter) {
181 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
182 self.first_iter = false;
183 }
184 const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
185 switch (posix.errno(rc)) {
186 .SUCCESS => {},
187 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
188 .FAULT => unreachable,
189 .NOTDIR => unreachable,
190 .INVAL => unreachable,
191 // Introduced in freebsd 13.2: directory unlinked but still open.
192 // To be consistent, iteration ends if the directory being iterated is deleted during iteration.
193 .NOENT => return null,
194 else => |err| return posix.unexpectedErrno(err),
195 }
196 if (rc == 0) return null;
197 self.index = 0;
198 self.end_index = @as(usize, @intCast(rc));
199 }
200 const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
201 const next_index = self.index +
202 if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen();
203 self.index = next_index;
204
205 const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen];
206
207 const skip_zero_fileno = switch (native_os) {
208 // fileno=0 is used to mark invalid entries or deleted files.
209 .openbsd, .netbsd => true,
210 else => false,
211 };
212 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
213 (skip_zero_fileno and bsd_entry.fileno == 0))
214 {
215 continue :start_over;
216 }
217
218 const entry_kind: Entry.Kind = switch (bsd_entry.type) {
219 posix.DT.BLK => .block_device,
220 posix.DT.CHR => .character_device,
221 posix.DT.DIR => .directory,
222 posix.DT.FIFO => .named_pipe,
223 posix.DT.LNK => .sym_link,
224 posix.DT.REG => .file,
225 posix.DT.SOCK => .unix_domain_socket,
226 posix.DT.WHT => .whiteout,
227 else => .unknown,
228 };
229 return Entry{
230 .name = name,
231 .kind = entry_kind,
232 };
233 }
234 }
235
236 pub fn reset(self: *Self) void {
237 self.index = 0;
238 self.end_index = 0;
239 self.first_iter = true;
240 }
241 },
242 .haiku => struct {
243 dir: Dir,
244 buf: [@sizeOf(DirEnt) + posix.PATH_MAX]u8 align(@alignOf(DirEnt)),
245 offset: usize,
246 index: usize,
247 end_index: usize,
248 first_iter: bool,
249
250 const Self = @This();
251 const DirEnt = posix.system.DirEnt;
252
253 pub const Error = IteratorError;
254
255 /// Memory such as file names referenced in this returned entry becomes invalid
256 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
257 pub fn next(self: *Self) Error!?Entry {
258 while (true) {
259 if (self.index >= self.end_index) {
260 if (self.first_iter) {
261 switch (@as(posix.E, @enumFromInt(posix.system._kern_rewind_dir(self.dir.fd)))) {
262 .SUCCESS => {},
263 .BADF => unreachable, // Dir is invalid
264 .FAULT => unreachable,
265 .NOTDIR => unreachable,
266 .INVAL => unreachable,
267 .ACCES => return error.AccessDenied,
268 .PERM => return error.PermissionDenied,
269 else => |err| return posix.unexpectedErrno(err),
270 }
271 self.first_iter = false;
272 }
273 const rc = posix.system._kern_read_dir(
274 self.dir.fd,
275 &self.buf,
276 self.buf.len,
277 self.buf.len / @sizeOf(DirEnt),
278 );
279 if (rc == 0) return null;
280 if (rc < 0) {
281 switch (@as(posix.E, @enumFromInt(rc))) {
282 .BADF => unreachable, // Dir is invalid
283 .FAULT => unreachable,
284 .NOTDIR => unreachable,
285 .INVAL => unreachable,
286 .OVERFLOW => unreachable,
287 .ACCES => return error.AccessDenied,
288 .PERM => return error.PermissionDenied,
289 else => |err| return posix.unexpectedErrno(err),
290 }
291 }
292 self.offset = 0;
293 self.index = 0;
294 self.end_index = @intCast(rc);
295 }
296 const dirent: *DirEnt = @ptrCast(@alignCast(&self.buf[self.offset]));
297 self.offset += dirent.reclen;
298 self.index += 1;
299 const name = mem.span(dirent.getName());
300 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or dirent.ino == 0) continue;
301
302 var stat_info: posix.Stat = undefined;
303 switch (@as(posix.E, @enumFromInt(posix.system._kern_read_stat(
304 self.dir.fd,
305 name,
306 false,
307 &stat_info,
308 @sizeOf(posix.Stat),
309 )))) {
310 .SUCCESS => {},
311 .INVAL => unreachable,
312 .BADF => unreachable, // Dir is invalid
313 .NOMEM => return error.SystemResources,
314 .ACCES => return error.AccessDenied,
315 .PERM => return error.PermissionDenied,
316 .FAULT => unreachable,
317 .NAMETOOLONG => unreachable,
318 .LOOP => unreachable,
319 .NOENT => continue,
320 else => |err| return posix.unexpectedErrno(err),
321 }
322 const statmode = stat_info.mode & posix.S.IFMT;
323
324 const entry_kind: Entry.Kind = switch (statmode) {
325 posix.S.IFDIR => .directory,
326 posix.S.IFBLK => .block_device,
327 posix.S.IFCHR => .character_device,
328 posix.S.IFLNK => .sym_link,
329 posix.S.IFREG => .file,
330 posix.S.IFIFO => .named_pipe,
331 else => .unknown,
332 };
333
334 return Entry{
335 .name = name,
336 .kind = entry_kind,
337 };
338 }
339 }
340
341 pub fn reset(self: *Self) void {
342 self.index = 0;
343 self.end_index = 0;
344 self.first_iter = true;
345 }
346 },
347 .linux => struct {
348 dir: Dir,
349 buf: [1024]u8 align(@alignOf(linux.dirent64)),
350 index: usize,
351 end_index: usize,
352 first_iter: bool,
353
354 const Self = @This();
355
356 pub const Error = IteratorError;
357
358 /// Memory such as file names referenced in this returned entry becomes invalid
359 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
360 pub fn next(self: *Self) Error!?Entry {
361 return self.nextLinux() catch |err| switch (err) {
362 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
363 // This matches the behavior of non-Linux UNIX platforms.
364 error.DirNotFound => null,
365 else => |e| return e,
366 };
367 }
368
369 pub const ErrorLinux = error{DirNotFound} || IteratorError;
370
371 /// Implementation of `next` that can return `error.DirNotFound` if the directory being
372 /// iterated was deleted during iteration (this error is Linux specific).
373 pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
374 start_over: while (true) {
375 if (self.index >= self.end_index) {
376 if (self.first_iter) {
377 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
378 self.first_iter = false;
379 }
380 const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
381 switch (linux.errno(rc)) {
382 .SUCCESS => {},
383 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
384 .FAULT => unreachable,
385 .NOTDIR => unreachable,
386 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
387 .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
388 .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory.
389 else => |err| return posix.unexpectedErrno(err),
390 }
391 if (rc == 0) return null;
392 self.index = 0;
393 self.end_index = rc;
394 }
395 const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
396 const next_index = self.index + linux_entry.reclen;
397 self.index = next_index;
398
399 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0);
400
401 // skip . and .. entries
402 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
403 continue :start_over;
404 }
405
406 const entry_kind: Entry.Kind = switch (linux_entry.type) {
407 linux.DT.BLK => .block_device,
408 linux.DT.CHR => .character_device,
409 linux.DT.DIR => .directory,
410 linux.DT.FIFO => .named_pipe,
411 linux.DT.LNK => .sym_link,
412 linux.DT.REG => .file,
413 linux.DT.SOCK => .unix_domain_socket,
414 else => .unknown,
415 };
416 return Entry{
417 .name = name,
418 .kind = entry_kind,
419 };
420 }
421 }
422
423 pub fn reset(self: *Self) void {
424 self.index = 0;
425 self.end_index = 0;
426 self.first_iter = true;
427 }
428 },
429 .windows => struct {
430 dir: Dir,
431 buf: [1024]u8 align(@alignOf(windows.FILE_BOTH_DIR_INFORMATION)),
432 index: usize,
433 end_index: usize,
434 first_iter: bool,
435 name_data: [fs.max_name_bytes]u8,
436
437 const Self = @This();
438
439 pub const Error = IteratorError;
440
441 /// Memory such as file names referenced in this returned entry becomes invalid
442 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
443 pub fn next(self: *Self) Error!?Entry {
444 const w = windows;
445 while (true) {
446 if (self.index >= self.end_index) {
447 var io: w.IO_STATUS_BLOCK = undefined;
448 const rc = w.ntdll.NtQueryDirectoryFile(
449 self.dir.fd,
450 null,
451 null,
452 null,
453 &io,
454 &self.buf,
455 self.buf.len,
456 .BothDirectory,
457 w.FALSE,
458 null,
459 @intFromBool(self.first_iter),
460 );
461 self.first_iter = false;
462 if (io.Information == 0) return null;
463 self.index = 0;
464 self.end_index = io.Information;
465 switch (rc) {
466 .SUCCESS => {},
467 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
468
469 else => return w.unexpectedStatus(rc),
470 }
471 }
472
473 // While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
474 // this may not always be the case (e.g. due to faulty VM/Sandboxing tools)
475 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
476 if (dir_info.NextEntryOffset != 0) {
477 self.index += dir_info.NextEntryOffset;
478 } else {
479 self.index = self.buf.len;
480 }
481
482 const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
483
484 if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' }))
485 continue;
486 const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
487 const name_wtf8 = self.name_data[0..name_wtf8_len];
488 const kind: Entry.Kind = blk: {
489 const attrs = dir_info.FileAttributes;
490 if (attrs.DIRECTORY) break :blk .directory;
491 if (attrs.REPARSE_POINT) break :blk .sym_link;
492 break :blk .file;
493 };
494 return Entry{
495 .name = name_wtf8,
496 .kind = kind,
497 };
498 }
499 }
500
501 pub fn reset(self: *Self) void {
502 self.index = 0;
503 self.end_index = 0;
504 self.first_iter = true;
505 }
506 },
507 .wasi => struct {
508 dir: Dir,
509 buf: [1024]u8 align(@alignOf(std.os.wasi.dirent_t)),
510 cookie: u64,
511 index: usize,
512 end_index: usize,
513
514 const Self = @This();
515
516 pub const Error = IteratorError;
517
518 /// Memory such as file names referenced in this returned entry becomes invalid
519 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
520 pub fn next(self: *Self) Error!?Entry {
521 return self.nextWasi() catch |err| switch (err) {
522 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
523 // This matches the behavior of non-Linux UNIX platforms.
524 error.DirNotFound => null,
525 else => |e| return e,
526 };
527 }
528
529 pub const ErrorWasi = error{DirNotFound} || IteratorError;
530
531 /// Implementation of `next` that can return platform-dependent errors depending on the host platform.
532 /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
533 /// iterated was deleted during iteration.
534 pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
535 // We intentinally use fd_readdir even when linked with libc,
536 // since its implementation is exactly the same as below,
537 // and we avoid the code complexity here.
538 const w = std.os.wasi;
539 start_over: while (true) {
540 // According to the WASI spec, the last entry might be truncated,
541 // so we need to check if the left buffer contains the whole dirent.
542 if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
543 var bufused: usize = undefined;
544 switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
545 .SUCCESS => {},
546 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
547 .FAULT => unreachable,
548 .NOTDIR => unreachable,
549 .INVAL => unreachable,
550 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
551 .NOTCAPABLE => return error.AccessDenied,
552 else => |err| return posix.unexpectedErrno(err),
553 }
554 if (bufused == 0) return null;
555 self.index = 0;
556 self.end_index = bufused;
557 }
558 const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index]));
559 const entry_size = @sizeOf(w.dirent_t);
560 const name_index = self.index + entry_size;
561 if (name_index + entry.namlen > self.end_index) {
562 // This case, the name is truncated, so we need to call readdir to store the entire name.
563 self.end_index = self.index; // Force fd_readdir in the next loop.
564 continue :start_over;
565 }
566 const name = self.buf[name_index .. name_index + entry.namlen];
567
568 const next_index = name_index + entry.namlen;
569 self.index = next_index;
570 self.cookie = entry.next;
571
572 // skip . and .. entries
573 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
574 continue :start_over;
575 }
576
577 const entry_kind: Entry.Kind = switch (entry.type) {
578 .BLOCK_DEVICE => .block_device,
579 .CHARACTER_DEVICE => .character_device,
580 .DIRECTORY => .directory,
581 .SYMBOLIC_LINK => .sym_link,
582 .REGULAR_FILE => .file,
583 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
584 else => .unknown,
585 };
586 return Entry{
587 .name = name,
588 .kind = entry_kind,
589 };
590 }
591 }
592
593 pub fn reset(self: *Self) void {
594 self.index = 0;
595 self.end_index = 0;
596 self.cookie = std.os.wasi.DIRCOOKIE_START;
597 }
598 },
599 else => @compileError("unimplemented"),
600};
601
602pub fn iterate(self: Dir) Iterator {
603 return self.iterateImpl(true);
604}
605
606/// Like `iterate`, but will not reset the directory cursor before the first
607/// iteration. This should only be used in cases where it is known that the
608/// `Dir` has not had its cursor modified yet (e.g. it was just opened).
609pub fn iterateAssumeFirstIteration(self: Dir) Iterator {
610 return self.iterateImpl(false);
611}
612
613fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator {
614 switch (native_os) {
615 .driverkit,
616 .ios,
617 .maccatalyst,
618 .macos,
619 .tvos,
620 .visionos,
621 .watchos,
622 .freebsd,
623 .netbsd,
624 .dragonfly,
625 .openbsd,
626 .illumos,
627 => return Iterator{
628 .dir = self,
629 .seek = 0,
630 .index = 0,
631 .end_index = 0,
632 .buf = undefined,
633 .first_iter = first_iter_start_value,
634 },
635 .linux => return Iterator{
636 .dir = self,
637 .index = 0,
638 .end_index = 0,
639 .buf = undefined,
640 .first_iter = first_iter_start_value,
641 },
642 .haiku => return Iterator{
643 .dir = self,
644 .offset = 0,
645 .index = 0,
646 .end_index = 0,
647 .buf = undefined,
648 .first_iter = first_iter_start_value,
649 },
650 .windows => return Iterator{
651 .dir = self,
652 .index = 0,
653 .end_index = 0,
654 .first_iter = first_iter_start_value,
655 .buf = undefined,
656 .name_data = undefined,
657 },
658 .wasi => return Iterator{
659 .dir = self,
660 .cookie = std.os.wasi.DIRCOOKIE_START,
661 .index = 0,
662 .end_index = 0,
663 .buf = undefined,
664 },
665 else => @compileError("unimplemented"),
666 }
667}
668
669pub const SelectiveWalker = struct {
670 stack: std.ArrayList(Walker.StackItem),
671 name_buffer: std.ArrayList(u8),
672 allocator: Allocator,
673
674 pub const Error = IteratorError || Allocator.Error;
675
676 /// After each call to this function, and on deinit(), the memory returned
677 /// from this function becomes invalid. A copy must be made in order to keep
678 /// a reference to the path.
679 pub fn next(self: *SelectiveWalker) Error!?Walker.Entry {
680 while (self.stack.items.len > 0) {
681 const top = &self.stack.items[self.stack.items.len - 1];
682 var dirname_len = top.dirname_len;
683 if (top.iter.next() catch |err| {
684 // If we get an error, then we want the user to be able to continue
685 // walking if they want, which means that we need to pop the directory
686 // that errored from the stack. Otherwise, all future `next` calls would
687 // likely just fail with the same error.
688 var item = self.stack.pop().?;
689 if (self.stack.items.len != 0) {
690 item.iter.dir.close();
691 }
692 return err;
693 }) |entry| {
694 self.name_buffer.shrinkRetainingCapacity(dirname_len);
695 if (self.name_buffer.items.len != 0) {
696 try self.name_buffer.append(self.allocator, fs.path.sep);
697 dirname_len += 1;
698 }
699 try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1);
700 self.name_buffer.appendSliceAssumeCapacity(entry.name);
701 self.name_buffer.appendAssumeCapacity(0);
702 const walker_entry: Walker.Entry = .{
703 .dir = top.iter.dir,
704 .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0],
705 .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0],
706 .kind = entry.kind,
707 };
708 return walker_entry;
709 } else {
710 var item = self.stack.pop().?;
711 if (self.stack.items.len != 0) {
712 item.iter.dir.close();
713 }
714 }
715 }
716 return null;
717 }
718
719 /// Traverses into the directory, continuing walking one level down.
720 pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void {
721 if (entry.kind != .directory) {
722 @branchHint(.cold);
723 return;
724 }
725
726 var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| {
727 switch (err) {
728 error.NameTooLong => unreachable,
729 else => |e| return e,
730 }
731 };
732 errdefer new_dir.close();
733
734 try self.stack.append(self.allocator, .{
735 .iter = new_dir.iterateAssumeFirstIteration(),
736 .dirname_len = self.name_buffer.items.len - 1,
737 });
738 }
739
740 pub fn deinit(self: *SelectiveWalker) void {
741 self.name_buffer.deinit(self.allocator);
742 self.stack.deinit(self.allocator);
743 }
744
745 /// Leaves the current directory, continuing walking one level up.
746 /// If the current entry is a directory entry, then the "current directory"
747 /// will pertain to that entry if `enter` is called before `leave`.
748 pub fn leave(self: *SelectiveWalker) void {
749 var item = self.stack.pop().?;
750 if (self.stack.items.len != 0) {
751 @branchHint(.likely);
752 item.iter.dir.close();
753 }
754 }
755};
756
757/// Recursively iterates over a directory, but requires the user to
758/// opt-in to recursing into each directory entry.
759///
760/// `self` must have been opened with `OpenOptions{.iterate = true}`.
761///
762/// `Walker.deinit` releases allocated memory and directory handles.
763///
764/// The order of returned file system entries is undefined.
765///
766/// `self` will not be closed after walking it.
767///
768/// See also `walk`.
769pub fn walkSelectively(self: Dir, allocator: Allocator) !SelectiveWalker {
770 var stack: std.ArrayList(Walker.StackItem) = .empty;
771
772 try stack.append(allocator, .{
773 .iter = self.iterate(),
774 .dirname_len = 0,
775 });
776
777 return .{
778 .stack = stack,
779 .name_buffer = .{},
780 .allocator = allocator,
781 };
782}
783
784pub const Walker = struct {
785 inner: SelectiveWalker,
786
787 pub const Entry = struct {
788 /// The containing directory. This can be used to operate directly on `basename`
789 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
790 /// The directory remains open until `next` or `deinit` is called.
791 dir: Dir,
792 basename: [:0]const u8,
793 path: [:0]const u8,
794 kind: Dir.Entry.Kind,
795
796 /// Returns the depth of the entry relative to the initial directory.
797 /// Returns 1 for a direct child of the initial directory, 2 for an entry
798 /// within a direct child of the initial directory, etc.
799 pub fn depth(self: Walker.Entry) usize {
800 return mem.countScalar(u8, self.path, fs.path.sep) + 1;
801 }
802 };
803
804 const StackItem = struct {
805 iter: Dir.Iterator,
806 dirname_len: usize,
807 };
808
809 /// After each call to this function, and on deinit(), the memory returned
810 /// from this function becomes invalid. A copy must be made in order to keep
811 /// a reference to the path.
812 pub fn next(self: *Walker) !?Walker.Entry {
813 const entry = try self.inner.next();
814 if (entry != null and entry.?.kind == .directory) {
815 try self.inner.enter(entry.?);
816 }
817 return entry;
818 }
819
820 pub fn deinit(self: *Walker) void {
821 self.inner.deinit();
822 }
823
824 /// Leaves the current directory, continuing walking one level up.
825 /// If the current entry is a directory entry, then the "current directory"
826 /// is the directory pertaining to the current entry.
827 pub fn leave(self: *Walker) void {
828 self.inner.leave();
829 }
830};
831
832/// Recursively iterates over a directory.
833///
834/// `self` must have been opened with `OpenOptions{.iterate = true}`.
835///
836/// `Walker.deinit` releases allocated memory and directory handles.
837///
838/// The order of returned file system entries is undefined.
839///
840/// `self` will not be closed after walking it.
841///
842/// See also `walkSelectively`.
843pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
844 return .{
845 .inner = try walkSelectively(self, allocator),
846 };
847}
848
849pub const OpenError = Io.Dir.OpenError;
850
851pub fn close(self: *Dir) void {
852 posix.close(self.fd);
853 self.* = undefined;
854}
855
856/// Deprecated in favor of `Io.Dir.openFile`.
857pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
858 var threaded: Io.Threaded = .init_single_threaded;
859 const io = threaded.ioBasic();
860 return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, flags));
861}
862
863/// Deprecated in favor of `Io.Dir.createFile`.
864pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
865 var threaded: Io.Threaded = .init_single_threaded;
866 const io = threaded.ioBasic();
867 const new_file = try Io.Dir.createFile(self.adaptToNewApi(), io, sub_path, flags);
868 return .adaptFromNewApi(new_file);
869}
870
871/// Deprecated in favor of `Io.Dir.MakeError`.
872pub const MakeError = Io.Dir.MakeError;
873
874/// Deprecated in favor of `Io.Dir.makeDir`.
875pub fn makeDir(self: Dir, sub_path: []const u8) MakeError!void {
876 var threaded: Io.Threaded = .init_single_threaded;
877 const io = threaded.ioBasic();
878 return Io.Dir.makeDir(.{ .handle = self.fd }, io, sub_path);
879}
880
881/// Deprecated in favor of `Io.Dir.makeDir`.
882pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) MakeError!void {
883 try posix.mkdiratZ(self.fd, sub_path, default_mode);
884}
885
886/// Deprecated in favor of `Io.Dir.makeDir`.
887pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {
888 try posix.mkdiratW(self.fd, mem.span(sub_path), default_mode);
889}
890
891/// Deprecated in favor of `Io.Dir.makePath`.
892pub fn makePath(self: Dir, sub_path: []const u8) MakePathError!void {
893 _ = try self.makePathStatus(sub_path);
894}
895
896/// Deprecated in favor of `Io.Dir.MakePathStatus`.
897pub const MakePathStatus = Io.Dir.MakePathStatus;
898/// Deprecated in favor of `Io.Dir.MakePathError`.
899pub const MakePathError = Io.Dir.MakePathError;
900
901/// Deprecated in favor of `Io.Dir.makePathStatus`.
902pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathStatus {
903 var threaded: Io.Threaded = .init_single_threaded;
904 const io = threaded.ioBasic();
905 return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path);
906}
907
908/// Deprecated in favor of `Io.Dir.makeOpenPath`.
909pub fn makeOpenPath(dir: Dir, sub_path: []const u8, options: OpenOptions) Io.Dir.MakeOpenPathError!Dir {
910 var threaded: Io.Threaded = .init_single_threaded;
911 const io = threaded.ioBasic();
912 return .adaptFromNewApi(try Io.Dir.makeOpenPath(dir.adaptToNewApi(), io, sub_path, options));
913}
914
915pub const RealPathError = posix.RealPathError || error{Canceled};
916
917/// This function returns the canonicalized absolute pathname of
918/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
919/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
920/// argument.
921/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
922/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
923/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
924/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
925/// This function is not universally supported by all platforms.
926/// Currently supported hosts are: Linux, macOS, and Windows.
927/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
928pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError![]u8 {
929 if (native_os == .wasi) {
930 @compileError("realpath is not available on WASI");
931 }
932 if (native_os == .windows) {
933 var pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname);
934
935 const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data);
936
937 const len = std.unicode.calcWtf8Len(wide_slice);
938 if (len > out_buffer.len)
939 return error.NameTooLong;
940
941 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
942 return out_buffer[0..end_index];
943 }
944 const pathname_c = try posix.toPosixPath(pathname);
945 return self.realpathZ(&pathname_c, out_buffer);
946}
947
948/// Same as `Dir.realpath` except `pathname` is null-terminated.
949/// See also `Dir.realpath`, `realpathZ`.
950pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 {
951 if (native_os == .windows) {
952 var pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname);
953
954 const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data);
955
956 const len = std.unicode.calcWtf8Len(wide_slice);
957 if (len > out_buffer.len)
958 return error.NameTooLong;
959
960 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
961 return out_buffer[0..end_index];
962 }
963
964 var flags: posix.O = .{};
965 if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true;
966 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
967 if (@hasField(posix.O, "PATH")) flags.PATH = true;
968
969 const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
970 error.FileLocksNotSupported => return error.Unexpected,
971 error.FileBusy => return error.Unexpected,
972 error.WouldBlock => return error.Unexpected,
973 else => |e| return e,
974 };
975 defer posix.close(fd);
976
977 var buffer: [fs.max_path_bytes]u8 = undefined;
978 const out_path = try std.os.getFdPath(fd, &buffer);
979
980 if (out_path.len > out_buffer.len) {
981 return error.NameTooLong;
982 }
983
984 const result = out_buffer[0..out_path.len];
985 @memcpy(result, out_path);
986 return result;
987}
988
989/// Deprecated: use `realpathW2`.
990///
991/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded.
992/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
993/// See also `Dir.realpath`, `realpathW`.
994pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
995 var wide_buf: [std.os.windows.PATH_MAX_WIDE]u16 = undefined;
996 const wide_slice = try self.realpathW2(pathname, &wide_buf);
997
998 const len = std.unicode.calcWtf8Len(wide_slice);
999 if (len > out_buffer.len) return error.NameTooLong;
1000
1001 const end_index = std.unicode.wtf16LeToWtf8(&out_buffer, wide_slice);
1002 return out_buffer[0..end_index];
1003}
1004
1005/// Windows-only. Same as `Dir.realpath` except
1006/// * `pathname` and the result are WTF-16 LE encoded
1007/// * `pathname` is relative or has the NT namespace prefix. See `windows.wToPrefixedFileW` for details.
1008///
1009/// Additionally, `pathname` will never be accessed after `out_buffer` has been written to, so it
1010/// is safe to reuse a single buffer for both.
1011///
1012/// See also `Dir.realpath`, `realpathW`.
1013pub fn realpathW2(self: Dir, pathname: []const u16, out_buffer: []u16) RealPathError![]u16 {
1014 const w = windows;
1015
1016 const h_file = blk: {
1017 const res = w.OpenFile(pathname, .{
1018 .dir = self.fd,
1019 .access_mask = .{
1020 .STANDARD = .{ .SYNCHRONIZE = true },
1021 .GENERIC = .{ .READ = true },
1022 },
1023 .creation = .OPEN,
1024 .filter = .any,
1025 }) catch |err| switch (err) {
1026 error.WouldBlock => unreachable,
1027 else => |e| return e,
1028 };
1029 break :blk res;
1030 };
1031 defer w.CloseHandle(h_file);
1032
1033 return w.GetFinalPathNameByHandle(h_file, .{}, out_buffer);
1034}
1035
1036pub const RealPathAllocError = RealPathError || Allocator.Error;
1037
1038/// Same as `Dir.realpath` except caller must free the returned memory.
1039/// See also `Dir.realpath`.
1040pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]u8 {
1041 // Use of max_path_bytes here is valid as the realpath function does not
1042 // have a variant that takes an arbitrary-size buffer.
1043 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1044 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1045 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1046 // anyway.
1047 var buf: [fs.max_path_bytes]u8 = undefined;
1048 return allocator.dupe(u8, try self.realpath(pathname, buf[0..]));
1049}
1050
1051/// Changes the current working directory to the open directory handle.
1052/// This modifies global state and can have surprising effects in multi-
1053/// threaded applications. Most applications and especially libraries should
1054/// not call this function as a general rule, however it can have use cases
1055/// in, for example, implementing a shell, or child process execution.
1056/// Not all targets support this. For example, WASI does not have the concept
1057/// of a current working directory.
1058pub fn setAsCwd(self: Dir) !void {
1059 if (native_os == .wasi) {
1060 @compileError("changing cwd is not currently possible in WASI");
1061 }
1062 if (native_os == .windows) {
1063 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
1064 const dir_path = try windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer);
1065 if (builtin.link_libc) {
1066 return posix.chdirW(dir_path);
1067 }
1068 return windows.SetCurrentDirectory(dir_path);
1069 }
1070 try posix.fchdir(self.fd);
1071}
1072
1073/// Deprecated in favor of `Io.Dir.OpenOptions`.
1074pub const OpenOptions = Io.Dir.OpenOptions;
1075
1076/// Deprecated in favor of `Io.Dir.openDir`.
1077pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir {
1078 var threaded: Io.Threaded = .init_single_threaded;
1079 const io = threaded.ioBasic();
1080 return .adaptFromNewApi(try Io.Dir.openDir(.{ .handle = self.fd }, io, sub_path, args));
1081}
1082
1083pub const DeleteFileError = posix.UnlinkError;
1084
1085/// Delete a file name and possibly the file it refers to, based on an open directory handle.
1086/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1087/// On WASI, `sub_path` should be encoded as valid UTF-8.
1088/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1089/// Asserts that the path parameter has no null bytes.
1090pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
1091 if (native_os == .windows) {
1092 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1093 return self.deleteFileW(sub_path_w.span());
1094 } else if (native_os == .wasi and !builtin.link_libc) {
1095 posix.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
1096 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1097 else => |e| return e,
1098 };
1099 } else {
1100 const sub_path_c = try posix.toPosixPath(sub_path);
1101 return self.deleteFileZ(&sub_path_c);
1102 }
1103}
1104
1105/// Same as `deleteFile` except the parameter is null-terminated.
1106pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
1107 posix.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
1108 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1109 error.AccessDenied, error.PermissionDenied => |e| switch (native_os) {
1110 // non-Linux POSIX systems return permission errors when trying to delete a
1111 // directory, so we need to handle that case specifically and translate the error
1112 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
1113 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
1114 const fstat = posix.fstatatZ(self.fd, sub_path_c, posix.AT.SYMLINK_NOFOLLOW) catch return e;
1115 const is_dir = fstat.mode & posix.S.IFMT == posix.S.IFDIR;
1116 return if (is_dir) error.IsDir else e;
1117 },
1118 else => return e,
1119 },
1120 else => |e| return e,
1121 };
1122}
1123
1124/// Same as `deleteFile` except the parameter is WTF-16 LE encoded.
1125pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
1126 posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
1127 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1128 else => |e| return e,
1129 };
1130}
1131
1132pub const DeleteDirError = error{
1133 DirNotEmpty,
1134 FileNotFound,
1135 AccessDenied,
1136 PermissionDenied,
1137 FileBusy,
1138 FileSystem,
1139 SymLinkLoop,
1140 NameTooLong,
1141 NotDir,
1142 SystemResources,
1143 ReadOnlyFileSystem,
1144 /// WASI: file paths must be valid UTF-8.
1145 /// Windows: file paths provided by the user must be valid WTF-8.
1146 /// https://wtf-8.codeberg.page/
1147 BadPathName,
1148 /// On Windows, `\\server` or `\\server\share` was not found.
1149 NetworkNotFound,
1150 ProcessNotFound,
1151 Unexpected,
1152};
1153
1154/// Returns `error.DirNotEmpty` if the directory is not empty.
1155/// To delete a directory recursively, see `deleteTree`.
1156/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1157/// On WASI, `sub_path` should be encoded as valid UTF-8.
1158/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1159/// Asserts that the path parameter has no null bytes.
1160pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
1161 if (native_os == .windows) {
1162 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1163 return self.deleteDirW(sub_path_w.span());
1164 } else if (native_os == .wasi and !builtin.link_libc) {
1165 posix.unlinkat(self.fd, sub_path, posix.AT.REMOVEDIR) catch |err| switch (err) {
1166 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1167 else => |e| return e,
1168 };
1169 } else {
1170 const sub_path_c = try posix.toPosixPath(sub_path);
1171 return self.deleteDirZ(&sub_path_c);
1172 }
1173}
1174
1175/// Same as `deleteDir` except the parameter is null-terminated.
1176pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
1177 posix.unlinkatZ(self.fd, sub_path_c, posix.AT.REMOVEDIR) catch |err| switch (err) {
1178 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1179 else => |e| return e,
1180 };
1181}
1182
1183/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed.
1184/// This function is Windows-only.
1185pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
1186 posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
1187 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1188 else => |e| return e,
1189 };
1190}
1191
1192pub const RenameError = posix.RenameError;
1193
1194/// Change the name or location of a file or directory.
1195/// If new_sub_path already exists, it will be replaced.
1196/// Renaming a file over an existing directory or a directory
1197/// over an existing file will fail with `error.IsDir` or `error.NotDir`
1198/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1199/// On WASI, both paths should be encoded as valid UTF-8.
1200/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1201pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
1202 return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
1203}
1204
1205/// Same as `rename` except the parameters are null-terminated.
1206pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
1207 return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
1208}
1209
1210/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
1211/// This function is Windows-only.
1212pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
1213 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w, windows.TRUE);
1214}
1215
1216/// Use with `Dir.symLink`, `Dir.atomicSymLink`, and `symLinkAbsolute` to
1217/// specify whether the symlink will point to a file or a directory. This value
1218/// is ignored on all hosts except Windows where creating symlinks to different
1219/// resource types, requires different flags. By default, `symLinkAbsolute` is
1220/// assumed to point to a file.
1221pub const SymLinkFlags = struct {
1222 is_directory: bool = false,
1223};
1224
1225/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1226/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1227/// one; the latter case is known as a dangling link.
1228/// If `sym_link_path` exists, it will not be overwritten.
1229/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1230/// On WASI, both paths should be encoded as valid UTF-8.
1231/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1232pub fn symLink(
1233 self: Dir,
1234 target_path: []const u8,
1235 sym_link_path: []const u8,
1236 flags: SymLinkFlags,
1237) !void {
1238 if (native_os == .wasi and !builtin.link_libc) {
1239 return self.symLinkWasi(target_path, sym_link_path, flags);
1240 }
1241 if (native_os == .windows) {
1242 // Target path does not use sliceToPrefixedFileW because certain paths
1243 // are handled differently when creating a symlink than they would be
1244 // when converting to an NT namespaced path. CreateSymbolicLink in
1245 // symLinkW will handle the necessary conversion.
1246 var target_path_w: windows.PathSpace = undefined;
1247 target_path_w.len = try windows.wtf8ToWtf16Le(&target_path_w.data, target_path);
1248 target_path_w.data[target_path_w.len] = 0;
1249 // However, we need to canonicalize any path separators to `\`, since if
1250 // the target path is relative, then it must use `\` as the path separator.
1251 mem.replaceScalar(
1252 u16,
1253 target_path_w.data[0..target_path_w.len],
1254 mem.nativeToLittle(u16, '/'),
1255 mem.nativeToLittle(u16, '\\'),
1256 );
1257
1258 const sym_link_path_w = try windows.sliceToPrefixedFileW(self.fd, sym_link_path);
1259 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1260 }
1261 const target_path_c = try posix.toPosixPath(target_path);
1262 const sym_link_path_c = try posix.toPosixPath(sym_link_path);
1263 return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
1264}
1265
1266/// WASI-only. Same as `symLink` except targeting WASI.
1267pub fn symLinkWasi(
1268 self: Dir,
1269 target_path: []const u8,
1270 sym_link_path: []const u8,
1271 _: SymLinkFlags,
1272) !void {
1273 return posix.symlinkat(target_path, self.fd, sym_link_path);
1274}
1275
1276/// Same as `symLink`, except the pathname parameters are null-terminated.
1277pub fn symLinkZ(
1278 self: Dir,
1279 target_path_c: [*:0]const u8,
1280 sym_link_path_c: [*:0]const u8,
1281 flags: SymLinkFlags,
1282) !void {
1283 if (native_os == .windows) {
1284 const target_path_w = try windows.cStrToPrefixedFileW(self.fd, target_path_c);
1285 const sym_link_path_w = try windows.cStrToPrefixedFileW(self.fd, sym_link_path_c);
1286 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1287 }
1288 return posix.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
1289}
1290
1291/// Windows-only. Same as `symLink` except the pathname parameters
1292/// are WTF16 LE encoded.
1293pub fn symLinkW(
1294 self: Dir,
1295 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
1296 /// of this path is handled by CreateSymbolicLink.
1297 /// Any path separators must be `\`, not `/`.
1298 target_path_w: [:0]const u16,
1299 /// WTF-16, must be NT-prefixed or relative
1300 sym_link_path_w: []const u16,
1301 flags: SymLinkFlags,
1302) !void {
1303 return windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
1304}
1305
1306/// Same as `symLink`, except tries to create the symbolic link until it
1307/// succeeds or encounters an error other than `error.PathAlreadyExists`.
1308///
1309/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1310/// * On WASI, both paths should be encoded as valid UTF-8.
1311/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1312pub fn atomicSymLink(
1313 dir: Dir,
1314 target_path: []const u8,
1315 sym_link_path: []const u8,
1316 flags: SymLinkFlags,
1317) !void {
1318 if (dir.symLink(target_path, sym_link_path, flags)) {
1319 return;
1320 } else |err| switch (err) {
1321 error.PathAlreadyExists => {},
1322 else => |e| return e,
1323 }
1324
1325 const dirname = path.dirname(sym_link_path) orelse ".";
1326
1327 const rand_len = @sizeOf(u64) * 2;
1328 const temp_path_len = dirname.len + 1 + rand_len;
1329 var temp_path_buf: [fs.max_path_bytes]u8 = undefined;
1330
1331 if (temp_path_len > temp_path_buf.len) return error.NameTooLong;
1332 @memcpy(temp_path_buf[0..dirname.len], dirname);
1333 temp_path_buf[dirname.len] = path.sep;
1334
1335 const temp_path = temp_path_buf[0..temp_path_len];
1336
1337 while (true) {
1338 const random_integer = std.crypto.random.int(u64);
1339 temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
1340
1341 if (dir.symLink(target_path, temp_path, flags)) {
1342 return dir.rename(temp_path, sym_link_path);
1343 } else |err| switch (err) {
1344 error.PathAlreadyExists => continue,
1345 else => |e| return e,
1346 }
1347 }
1348}
1349
1350pub const ReadLinkError = posix.ReadLinkError;
1351
1352/// Read value of a symbolic link.
1353/// The return value is a slice of `buffer`, from index `0`.
1354/// Asserts that the path parameter has no null bytes.
1355/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1356/// On WASI, `sub_path` should be encoded as valid UTF-8.
1357/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1358pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
1359 if (native_os == .wasi and !builtin.link_libc) {
1360 return self.readLinkWasi(sub_path, buffer);
1361 }
1362 if (native_os == .windows) {
1363 var sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1364 const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
1365
1366 const len = std.unicode.calcWtf8Len(result_w);
1367 if (len > buffer.len) return error.NameTooLong;
1368
1369 const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
1370 return buffer[0..end_index];
1371 }
1372 const sub_path_c = try posix.toPosixPath(sub_path);
1373 return self.readLinkZ(&sub_path_c, buffer);
1374}
1375
1376/// WASI-only. Same as `readLink` except targeting WASI.
1377pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1378 return posix.readlinkat(self.fd, sub_path, buffer);
1379}
1380
1381/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
1382pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
1383 if (native_os == .windows) {
1384 var sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1385 const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
1386
1387 const len = std.unicode.calcWtf8Len(result_w);
1388 if (len > buffer.len) return error.NameTooLong;
1389
1390 const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
1391 return buffer[0..end_index];
1392 }
1393 return posix.readlinkatZ(self.fd, sub_path_c, buffer);
1394}
1395
1396/// Windows-only. Same as `readLink` except the path parameter
1397/// is WTF-16 LE encoded, NT-prefixed.
1398///
1399/// `sub_path_w` will never be accessed after `buffer` has been written to, so it
1400/// is safe to reuse a single buffer for both.
1401pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u16) ![]u16 {
1402 return windows.ReadLink(self.fd, sub_path_w, buffer);
1403}
1404
1405/// Deprecated in favor of `Io.Dir.readFile`.
1406pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
1407 var threaded: Io.Threaded = .init_single_threaded;
1408 const io = threaded.ioBasic();
1409 return Io.Dir.readFile(.{ .handle = self.fd }, io, file_path, buffer);
1410}
1411
1412pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{
1413 /// File size reached or exceeded the provided limit.
1414 StreamTooLong,
1415};
1416
1417/// Reads all the bytes from the named file. On success, caller owns returned
1418/// buffer.
1419///
1420/// If the file size is already known, a better alternative is to initialize a
1421/// `File.Reader`.
1422///
1423/// If the file size cannot be obtained, an error is returned. If
1424/// this is a realistic possibility, a better alternative is to initialize a
1425/// `File.Reader` which handles this seamlessly.
1426pub fn readFileAlloc(
1427 dir: Dir,
1428 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1429 /// On WASI, should be encoded as valid UTF-8.
1430 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1431 sub_path: []const u8,
1432 /// Used to allocate the result.
1433 gpa: Allocator,
1434 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1435 limit: Io.Limit,
1436) ReadFileAllocError![]u8 {
1437 return readFileAllocOptions(dir, sub_path, gpa, limit, .of(u8), null);
1438}
1439
1440/// Reads all the bytes from the named file. On success, caller owns returned
1441/// buffer.
1442///
1443/// If the file size is already known, a better alternative is to initialize a
1444/// `File.Reader`.
1445///
1446/// TODO move this function to Io.Dir
1447pub fn readFileAllocOptions(
1448 dir: Dir,
1449 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1450 /// On WASI, should be encoded as valid UTF-8.
1451 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1452 sub_path: []const u8,
1453 /// Used to allocate the result.
1454 gpa: Allocator,
1455 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1456 limit: Io.Limit,
1457 comptime alignment: std.mem.Alignment,
1458 comptime sentinel: ?u8,
1459) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
1460 var threaded: Io.Threaded = .init_single_threaded;
1461 const io = threaded.ioBasic();
1462
1463 var file = try dir.openFile(sub_path, .{});
1464 defer file.close();
1465 var file_reader = file.reader(io, &.{});
1466 return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
1467 error.ReadFailed => return file_reader.err.?,
1468 error.OutOfMemory, error.StreamTooLong => |e| return e,
1469 };
1470}
1471
1472pub const DeleteTreeError = error{
1473 AccessDenied,
1474 PermissionDenied,
1475 FileTooBig,
1476 SymLinkLoop,
1477 ProcessFdQuotaExceeded,
1478 NameTooLong,
1479 SystemFdQuotaExceeded,
1480 NoDevice,
1481 SystemResources,
1482 ReadOnlyFileSystem,
1483 FileSystem,
1484 FileBusy,
1485 DeviceBusy,
1486 ProcessNotFound,
1487 /// One of the path components was not a directory.
1488 /// This error is unreachable if `sub_path` does not contain a path separator.
1489 NotDir,
1490 /// WASI: file paths must be valid UTF-8.
1491 /// Windows: file paths provided by the user must be valid WTF-8.
1492 /// https://wtf-8.codeberg.page/
1493 /// On Windows, file paths cannot contain these characters:
1494 /// '/', '*', '?', '"', '<', '>', '|'
1495 BadPathName,
1496 /// On Windows, `\\server` or `\\server\share` was not found.
1497 NetworkNotFound,
1498
1499 Canceled,
1500} || posix.UnexpectedError;
1501
1502/// Whether `sub_path` describes a symlink, file, or directory, this function
1503/// removes it. If it cannot be removed because it is a non-empty directory,
1504/// this function recursively removes its entries and then tries again.
1505/// This operation is not atomic on most file systems.
1506/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1507/// On WASI, `sub_path` should be encoded as valid UTF-8.
1508/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1509pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1510 var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
1511
1512 const StackItem = struct {
1513 name: []const u8,
1514 parent_dir: Dir,
1515 iter: Dir.Iterator,
1516
1517 fn closeAll(items: []@This()) void {
1518 for (items) |*item| item.iter.dir.close();
1519 }
1520 };
1521
1522 var stack_buffer: [16]StackItem = undefined;
1523 var stack = std.ArrayList(StackItem).initBuffer(&stack_buffer);
1524 defer StackItem.closeAll(stack.items);
1525
1526 stack.appendAssumeCapacity(.{
1527 .name = sub_path,
1528 .parent_dir = self,
1529 .iter = initial_iterable_dir.iterateAssumeFirstIteration(),
1530 });
1531
1532 process_stack: while (stack.items.len != 0) {
1533 var top = &stack.items[stack.items.len - 1];
1534 while (try top.iter.next()) |entry| {
1535 var treat_as_dir = entry.kind == .directory;
1536 handle_entry: while (true) {
1537 if (treat_as_dir) {
1538 if (stack.unusedCapacitySlice().len >= 1) {
1539 var iterable_dir = top.iter.dir.openDir(entry.name, .{
1540 .follow_symlinks = false,
1541 .iterate = true,
1542 }) catch |err| switch (err) {
1543 error.NotDir => {
1544 treat_as_dir = false;
1545 continue :handle_entry;
1546 },
1547 error.FileNotFound => {
1548 // That's fine, we were trying to remove this directory anyway.
1549 break :handle_entry;
1550 },
1551
1552 error.AccessDenied,
1553 error.PermissionDenied,
1554 error.SymLinkLoop,
1555 error.ProcessFdQuotaExceeded,
1556 error.NameTooLong,
1557 error.SystemFdQuotaExceeded,
1558 error.NoDevice,
1559 error.SystemResources,
1560 error.Unexpected,
1561 error.BadPathName,
1562 error.NetworkNotFound,
1563 error.DeviceBusy,
1564 error.Canceled,
1565 => |e| return e,
1566 };
1567 stack.appendAssumeCapacity(.{
1568 .name = entry.name,
1569 .parent_dir = top.iter.dir,
1570 .iter = iterable_dir.iterateAssumeFirstIteration(),
1571 });
1572 continue :process_stack;
1573 } else {
1574 try top.iter.dir.deleteTreeMinStackSizeWithKindHint(entry.name, entry.kind);
1575 break :handle_entry;
1576 }
1577 } else {
1578 if (top.iter.dir.deleteFile(entry.name)) {
1579 break :handle_entry;
1580 } else |err| switch (err) {
1581 error.FileNotFound => break :handle_entry,
1582
1583 // Impossible because we do not pass any path separators.
1584 error.NotDir => unreachable,
1585
1586 error.IsDir => {
1587 treat_as_dir = true;
1588 continue :handle_entry;
1589 },
1590
1591 error.AccessDenied,
1592 error.PermissionDenied,
1593 error.SymLinkLoop,
1594 error.NameTooLong,
1595 error.SystemResources,
1596 error.ReadOnlyFileSystem,
1597 error.FileSystem,
1598 error.FileBusy,
1599 error.BadPathName,
1600 error.NetworkNotFound,
1601 error.Unexpected,
1602 => |e| return e,
1603 }
1604 }
1605 }
1606 }
1607
1608 // On Windows, we can't delete until the dir's handle has been closed, so
1609 // close it before we try to delete.
1610 top.iter.dir.close();
1611
1612 // In order to avoid double-closing the directory when cleaning up
1613 // the stack in the case of an error, we save the relevant portions and
1614 // pop the value from the stack.
1615 const parent_dir = top.parent_dir;
1616 const name = top.name;
1617 stack.items.len -= 1;
1618
1619 var need_to_retry: bool = false;
1620 parent_dir.deleteDir(name) catch |err| switch (err) {
1621 error.FileNotFound => {},
1622 error.DirNotEmpty => need_to_retry = true,
1623 else => |e| return e,
1624 };
1625
1626 if (need_to_retry) {
1627 // Since we closed the handle that the previous iterator used, we
1628 // need to re-open the dir and re-create the iterator.
1629 var iterable_dir = iterable_dir: {
1630 var treat_as_dir = true;
1631 handle_entry: while (true) {
1632 if (treat_as_dir) {
1633 break :iterable_dir parent_dir.openDir(name, .{
1634 .follow_symlinks = false,
1635 .iterate = true,
1636 }) catch |err| switch (err) {
1637 error.NotDir => {
1638 treat_as_dir = false;
1639 continue :handle_entry;
1640 },
1641 error.FileNotFound => {
1642 // That's fine, we were trying to remove this directory anyway.
1643 continue :process_stack;
1644 },
1645
1646 error.AccessDenied,
1647 error.PermissionDenied,
1648 error.SymLinkLoop,
1649 error.ProcessFdQuotaExceeded,
1650 error.NameTooLong,
1651 error.SystemFdQuotaExceeded,
1652 error.NoDevice,
1653 error.SystemResources,
1654 error.Unexpected,
1655 error.BadPathName,
1656 error.NetworkNotFound,
1657 error.DeviceBusy,
1658 error.Canceled,
1659 => |e| return e,
1660 };
1661 } else {
1662 if (parent_dir.deleteFile(name)) {
1663 continue :process_stack;
1664 } else |err| switch (err) {
1665 error.FileNotFound => continue :process_stack,
1666
1667 // Impossible because we do not pass any path separators.
1668 error.NotDir => unreachable,
1669
1670 error.IsDir => {
1671 treat_as_dir = true;
1672 continue :handle_entry;
1673 },
1674
1675 error.AccessDenied,
1676 error.PermissionDenied,
1677 error.SymLinkLoop,
1678 error.NameTooLong,
1679 error.SystemResources,
1680 error.ReadOnlyFileSystem,
1681 error.FileSystem,
1682 error.FileBusy,
1683 error.BadPathName,
1684 error.NetworkNotFound,
1685 error.Unexpected,
1686 => |e| return e,
1687 }
1688 }
1689 }
1690 };
1691 // We know there is room on the stack since we are just re-adding
1692 // the StackItem that we previously popped.
1693 stack.appendAssumeCapacity(.{
1694 .name = name,
1695 .parent_dir = parent_dir,
1696 .iter = iterable_dir.iterateAssumeFirstIteration(),
1697 });
1698 continue :process_stack;
1699 }
1700 }
1701}
1702
1703/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
1704/// This is slower than `deleteTree` but uses less stack space.
1705/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1706/// On WASI, `sub_path` should be encoded as valid UTF-8.
1707/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1708pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1709 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
1710}
1711
1712fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
1713 start_over: while (true) {
1714 var dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
1715 var cleanup_dir_parent: ?Dir = null;
1716 defer if (cleanup_dir_parent) |*d| d.close();
1717
1718 var cleanup_dir = true;
1719 defer if (cleanup_dir) dir.close();
1720
1721 // Valid use of max_path_bytes because dir_name_buf will only
1722 // ever store a single path component that was returned from the
1723 // filesystem.
1724 var dir_name_buf: [fs.max_path_bytes]u8 = undefined;
1725 var dir_name: []const u8 = sub_path;
1726
1727 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
1728 // Go through each entry and if it is not a directory, delete it. If it is a directory,
1729 // open it, and close the original directory. Repeat. Then start the entire operation over.
1730
1731 scan_dir: while (true) {
1732 var dir_it = dir.iterateAssumeFirstIteration();
1733 dir_it: while (try dir_it.next()) |entry| {
1734 var treat_as_dir = entry.kind == .directory;
1735 handle_entry: while (true) {
1736 if (treat_as_dir) {
1737 const new_dir = dir.openDir(entry.name, .{
1738 .follow_symlinks = false,
1739 .iterate = true,
1740 }) catch |err| switch (err) {
1741 error.NotDir => {
1742 treat_as_dir = false;
1743 continue :handle_entry;
1744 },
1745 error.FileNotFound => {
1746 // That's fine, we were trying to remove this directory anyway.
1747 continue :dir_it;
1748 },
1749
1750 error.AccessDenied,
1751 error.PermissionDenied,
1752 error.SymLinkLoop,
1753 error.ProcessFdQuotaExceeded,
1754 error.NameTooLong,
1755 error.SystemFdQuotaExceeded,
1756 error.NoDevice,
1757 error.SystemResources,
1758 error.Unexpected,
1759 error.BadPathName,
1760 error.NetworkNotFound,
1761 error.DeviceBusy,
1762 error.Canceled,
1763 => |e| return e,
1764 };
1765 if (cleanup_dir_parent) |*d| d.close();
1766 cleanup_dir_parent = dir;
1767 dir = new_dir;
1768 const result = dir_name_buf[0..entry.name.len];
1769 @memcpy(result, entry.name);
1770 dir_name = result;
1771 continue :scan_dir;
1772 } else {
1773 if (dir.deleteFile(entry.name)) {
1774 continue :dir_it;
1775 } else |err| switch (err) {
1776 error.FileNotFound => continue :dir_it,
1777
1778 // Impossible because we do not pass any path separators.
1779 error.NotDir => unreachable,
1780
1781 error.IsDir => {
1782 treat_as_dir = true;
1783 continue :handle_entry;
1784 },
1785
1786 error.AccessDenied,
1787 error.PermissionDenied,
1788 error.SymLinkLoop,
1789 error.NameTooLong,
1790 error.SystemResources,
1791 error.ReadOnlyFileSystem,
1792 error.FileSystem,
1793 error.FileBusy,
1794 error.BadPathName,
1795 error.NetworkNotFound,
1796 error.Unexpected,
1797 => |e| return e,
1798 }
1799 }
1800 }
1801 }
1802 // Reached the end of the directory entries, which means we successfully deleted all of them.
1803 // Now to remove the directory itself.
1804 dir.close();
1805 cleanup_dir = false;
1806
1807 if (cleanup_dir_parent) |d| {
1808 d.deleteDir(dir_name) catch |err| switch (err) {
1809 // These two things can happen due to file system race conditions.
1810 error.FileNotFound, error.DirNotEmpty => continue :start_over,
1811 else => |e| return e,
1812 };
1813 continue :start_over;
1814 } else {
1815 self.deleteDir(sub_path) catch |err| switch (err) {
1816 error.FileNotFound => return,
1817 error.DirNotEmpty => continue :start_over,
1818 else => |e| return e,
1819 };
1820 return;
1821 }
1822 }
1823 }
1824}
1825
1826/// On successful delete, returns null.
1827fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
1828 return iterable_dir: {
1829 // Treat as a file by default
1830 var treat_as_dir = kind_hint == .directory;
1831
1832 handle_entry: while (true) {
1833 if (treat_as_dir) {
1834 break :iterable_dir self.openDir(sub_path, .{
1835 .follow_symlinks = false,
1836 .iterate = true,
1837 }) catch |err| switch (err) {
1838 error.NotDir => {
1839 treat_as_dir = false;
1840 continue :handle_entry;
1841 },
1842 error.FileNotFound => {
1843 // That's fine, we were trying to remove this directory anyway.
1844 return null;
1845 },
1846
1847 error.AccessDenied,
1848 error.PermissionDenied,
1849 error.SymLinkLoop,
1850 error.ProcessFdQuotaExceeded,
1851 error.NameTooLong,
1852 error.SystemFdQuotaExceeded,
1853 error.NoDevice,
1854 error.SystemResources,
1855 error.Unexpected,
1856 error.BadPathName,
1857 error.DeviceBusy,
1858 error.NetworkNotFound,
1859 error.Canceled,
1860 => |e| return e,
1861 };
1862 } else {
1863 if (self.deleteFile(sub_path)) {
1864 return null;
1865 } else |err| switch (err) {
1866 error.FileNotFound => return null,
1867
1868 error.IsDir => {
1869 treat_as_dir = true;
1870 continue :handle_entry;
1871 },
1872
1873 error.AccessDenied,
1874 error.PermissionDenied,
1875 error.SymLinkLoop,
1876 error.NameTooLong,
1877 error.SystemResources,
1878 error.ReadOnlyFileSystem,
1879 error.NotDir,
1880 error.FileSystem,
1881 error.FileBusy,
1882 error.BadPathName,
1883 error.NetworkNotFound,
1884 error.Unexpected,
1885 => |e| return e,
1886 }
1887 }
1888 }
1889 };
1890}
1891
1892pub const WriteFileError = File.WriteError || File.OpenError;
1893
1894pub const WriteFileOptions = struct {
1895 /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1896 /// On WASI, `sub_path` should be encoded as valid UTF-8.
1897 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1898 sub_path: []const u8,
1899 data: []const u8,
1900 flags: File.CreateFlags = .{},
1901};
1902
1903/// Writes content to the file system, using the file creation flags provided.
1904pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void {
1905 var file = try self.createFile(options.sub_path, options.flags);
1906 defer file.close();
1907 try file.writeAll(options.data);
1908}
1909
1910/// Deprecated in favor of `Io.Dir.AccessError`.
1911pub const AccessError = Io.Dir.AccessError;
1912
1913/// Deprecated in favor of `Io.Dir.access`.
1914pub fn access(self: Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) AccessError!void {
1915 var threaded: Io.Threaded = .init_single_threaded;
1916 const io = threaded.ioBasic();
1917 return Io.Dir.access(self.adaptToNewApi(), io, sub_path, options);
1918}
1919
1920pub const CopyFileOptions = struct {
1921 /// When this is `null` the mode is copied from the source file.
1922 override_mode: ?File.Mode = null,
1923};
1924
1925pub const CopyFileError = File.OpenError || File.StatError ||
1926 AtomicFile.InitError || AtomicFile.FinishError ||
1927 File.ReadError || File.WriteError || error{InvalidFileName};
1928
1929/// Atomically creates a new file at `dest_path` within `dest_dir` with the
1930/// same contents as `source_path` within `source_dir`, overwriting any already
1931/// existing file.
1932///
1933/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
1934/// readily available, there is a possibility of power loss or application
1935/// termination leaving temporary files present in the same directory as
1936/// dest_path.
1937///
1938/// On Windows, both paths should be encoded as
1939/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be
1940/// encoded as valid UTF-8. On other platforms, both paths are an opaque
1941/// sequence of bytes with no particular encoding.
1942///
1943/// TODO move this function to Io.Dir
1944pub fn copyFile(
1945 source_dir: Dir,
1946 source_path: []const u8,
1947 dest_dir: Dir,
1948 dest_path: []const u8,
1949 options: CopyFileOptions,
1950) CopyFileError!void {
1951 var threaded: Io.Threaded = .init_single_threaded;
1952 const io = threaded.ioBasic();
1953
1954 const file = try source_dir.openFile(source_path, .{});
1955 var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
1956 defer file_reader.file.close(io);
1957
1958 const mode = options.override_mode orelse blk: {
1959 const st = try file_reader.file.stat(io);
1960 file_reader.size = st.size;
1961 break :blk st.mode;
1962 };
1963
1964 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
1965 var atomic_file = try dest_dir.atomicFile(dest_path, .{
1966 .mode = mode,
1967 .write_buffer = &buffer,
1968 });
1969 defer atomic_file.deinit();
1970
1971 _ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
1972 error.ReadFailed => return file_reader.err.?,
1973 error.WriteFailed => return atomic_file.file_writer.err.?,
1974 };
1975
1976 try atomic_file.finish();
1977}
1978
1979pub const AtomicFileOptions = struct {
1980 mode: File.Mode = File.default_mode,
1981 make_path: bool = false,
1982 write_buffer: []u8,
1983};
1984
1985/// Directly access the `.file` field, and then call `AtomicFile.finish` to
1986/// atomically replace `dest_path` with contents.
1987/// Always call `AtomicFile.deinit` to clean up, regardless of whether
1988/// `AtomicFile.finish` succeeded. `dest_path` must remain valid until
1989/// `AtomicFile.deinit` is called.
1990/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1991/// On WASI, `dest_path` should be encoded as valid UTF-8.
1992/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
1993pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
1994 if (fs.path.dirname(dest_path)) |dirname| {
1995 const dir = if (options.make_path)
1996 try self.makeOpenPath(dirname, .{})
1997 else
1998 try self.openDir(dirname, .{});
1999
2000 return .init(fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer);
2001 } else {
2002 return .init(dest_path, options.mode, self, false, options.write_buffer);
2003 }
2004}
2005
2006pub const Stat = File.Stat;
2007pub const StatError = File.StatError;
2008
2009/// Deprecated in favor of `Io.Dir.stat`.
2010pub fn stat(self: Dir) StatError!Stat {
2011 const file: File = .{ .handle = self.fd };
2012 return file.stat();
2013}
2014
2015pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;
2016
2017/// Deprecated in favor of `Io.Dir.statPath`.
2018pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2019 var threaded: Io.Threaded = .init_single_threaded;
2020 const io = threaded.ioBasic();
2021 return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{});
2022}
2023
2024pub const ChmodError = File.ChmodError;
2025
2026/// Changes the mode of the directory.
2027/// The process must have the correct privileges in order to do this
2028/// successfully, or must have the effective user ID matching the owner
2029/// of the directory. Additionally, the directory must have been opened
2030/// with `OpenOptions{ .iterate = true }`.
2031pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
2032 const file: File = .{ .handle = self.fd };
2033 try file.chmod(new_mode);
2034}
2035
2036/// Changes the owner and group of the directory.
2037/// The process must have the correct privileges in order to do this
2038/// successfully. The group may be changed by the owner of the directory to
2039/// any group of which the owner is a member. Additionally, the directory
2040/// must have been opened with `OpenOptions{ .iterate = true }`. If the
2041/// owner or group is specified as `null`, the ID is not changed.
2042pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
2043 const file: File = .{ .handle = self.fd };
2044 try file.chown(owner, group);
2045}
2046
2047pub const ChownError = File.ChownError;
2048
2049const Permissions = File.Permissions;
2050pub const SetPermissionsError = File.SetPermissionsError;
2051
2052/// Sets permissions according to the provided `Permissions` struct.
2053/// This method is *NOT* available on WASI
2054pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void {
2055 const file: File = .{ .handle = self.fd };
2056 try file.setPermissions(permissions);
2057}
2058
2059pub fn adaptToNewApi(dir: Dir) Io.Dir {
2060 return .{ .handle = dir.fd };
2061}
2062
2063pub fn adaptFromNewApi(dir: Io.Dir) Dir {
2064 return .{ .fd = dir.handle };
2065}
lib/std/fs/File.zig deleted-1437
......@@ -1,1437 +0,0 @@
1const File = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6
7const std = @import("../std.zig");
8const Io = std.Io;
9const Os = std.builtin.Os;
10const Allocator = std.mem.Allocator;
11const posix = std.posix;
12const math = std.math;
13const assert = std.debug.assert;
14const linux = std.os.linux;
15const windows = std.os.windows;
16const maxInt = std.math.maxInt;
17const Alignment = std.mem.Alignment;
18
19/// The OS-specific file descriptor or file handle.
20handle: Handle,
21
22pub const Handle = Io.File.Handle;
23pub const Mode = Io.File.Mode;
24pub const INode = Io.File.INode;
25pub const Uid = posix.uid_t;
26pub const Gid = posix.gid_t;
27pub const Kind = Io.File.Kind;
28
29/// This is the default mode given to POSIX operating systems for creating
30/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
31/// since most people would expect "-rw-r--r--", for example, when using
32/// the `touch` command, which would correspond to `0o644`. However, POSIX
33/// libc implementations use `0o666` inside `fopen` and then rely on the
34/// process-scoped "umask" setting to adjust this number for file creation.
35pub const default_mode: Mode = if (Mode == u0) 0 else 0o666;
36
37/// Deprecated in favor of `Io.File.OpenError`.
38pub const OpenError = Io.File.OpenError || error{WouldBlock};
39/// Deprecated in favor of `Io.File.OpenMode`.
40pub const OpenMode = Io.File.OpenMode;
41/// Deprecated in favor of `Io.File.Lock`.
42pub const Lock = Io.File.Lock;
43/// Deprecated in favor of `Io.File.OpenFlags`.
44pub const OpenFlags = Io.File.OpenFlags;
45
46pub const CreateFlags = struct {
47 /// Whether the file will be created with read access.
48 read: bool = false,
49
50 /// If the file already exists, and is a regular file, and the access
51 /// mode allows writing, it will be truncated to length 0.
52 truncate: bool = true,
53
54 /// Ensures that this open call creates the file, otherwise causes
55 /// `error.PathAlreadyExists` to be returned.
56 exclusive: bool = false,
57
58 /// Open the file with an advisory lock to coordinate with other processes
59 /// accessing it at the same time. An exclusive lock will prevent other
60 /// processes from acquiring a lock. A shared lock will prevent other
61 /// processes from acquiring a exclusive lock, but does not prevent
62 /// other process from getting their own shared locks.
63 ///
64 /// The lock is advisory, except on Linux in very specific circumstances[1].
65 /// This means that a process that does not respect the locking API can still get access
66 /// to the file, despite the lock.
67 ///
68 /// On these operating systems, the lock is acquired atomically with
69 /// opening the file:
70 /// * Darwin
71 /// * DragonFlyBSD
72 /// * FreeBSD
73 /// * Haiku
74 /// * NetBSD
75 /// * OpenBSD
76 /// On these operating systems, the lock is acquired via a separate syscall
77 /// after opening the file:
78 /// * Linux
79 /// * Windows
80 ///
81 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
82 lock: Lock = .none,
83
84 /// Sets whether or not to wait until the file is locked to return. If set to true,
85 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
86 /// is available to proceed.
87 lock_nonblocking: bool = false,
88
89 /// For POSIX systems this is the file system mode the file will
90 /// be created with. On other systems this is always 0.
91 mode: Mode = default_mode,
92};
93
94pub fn stdout() File {
95 return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdOutput else posix.STDOUT_FILENO };
96}
97
98pub fn stderr() File {
99 return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdError else posix.STDERR_FILENO };
100}
101
102pub fn stdin() File {
103 return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdInput else posix.STDIN_FILENO };
104}
105
106/// Upon success, the stream is in an uninitialized state. To continue using it,
107/// you must use the open() function.
108pub fn close(self: File) void {
109 if (is_windows) {
110 windows.CloseHandle(self.handle);
111 } else {
112 posix.close(self.handle);
113 }
114}
115
116pub const SyncError = posix.SyncError;
117
118/// Blocks until all pending file contents and metadata modifications
119/// for the file have been synchronized with the underlying filesystem.
120///
121/// Note that this does not ensure that metadata for the
122/// directory containing the file has also reached disk.
123pub fn sync(self: File) SyncError!void {
124 return posix.fsync(self.handle);
125}
126
127/// Test whether the file refers to a terminal.
128/// See also `getOrEnableAnsiEscapeSupport` and `supportsAnsiEscapeCodes`.
129pub fn isTty(self: File) bool {
130 return posix.isatty(self.handle);
131}
132
133pub fn isCygwinPty(file: File) bool {
134 if (builtin.os.tag != .windows) return false;
135
136 const handle = file.handle;
137
138 // If this is a MSYS2/cygwin pty, then it will be a named pipe with a name in one of these formats:
139 // msys-[...]-ptyN-[...]
140 // cygwin-[...]-ptyN-[...]
141 //
142 // Example: msys-1888ae32e00d56aa-pty0-to-master
143
144 // First, just check that the handle is a named pipe.
145 // This allows us to avoid the more costly NtQueryInformationFile call
146 // for handles that aren't named pipes.
147 {
148 var io_status: windows.IO_STATUS_BLOCK = undefined;
149 var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined;
150 const rc = windows.ntdll.NtQueryVolumeInformationFile(handle, &io_status, &device_info, @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), .Device);
151 switch (rc) {
152 .SUCCESS => {},
153 else => return false,
154 }
155 if (device_info.DeviceType.FileDevice != .NAMED_PIPE) return false;
156 }
157
158 const name_bytes_offset = @offsetOf(windows.FILE_NAME_INFO, "FileName");
159 // `NAME_MAX` UTF-16 code units (2 bytes each)
160 // This buffer may not be long enough to handle *all* possible paths
161 // (PATH_MAX_WIDE would be necessary for that), but because we only care
162 // about certain paths and we know they must be within a reasonable length,
163 // we can use this smaller buffer and just return false on any error from
164 // NtQueryInformationFile.
165 const num_name_bytes = windows.MAX_PATH * 2;
166 var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (name_bytes_offset + num_name_bytes);
167
168 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
169 const rc = windows.ntdll.NtQueryInformationFile(handle, &io_status_block, &name_info_bytes, @intCast(name_info_bytes.len), .Name);
170 switch (rc) {
171 .SUCCESS => {},
172 .INVALID_PARAMETER => unreachable,
173 else => return false,
174 }
175
176 const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes);
177 const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength];
178 const name_wide = std.mem.bytesAsSlice(u16, name_bytes);
179 // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master
180 return (std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'm', 's', 'y', 's', '-' }) or
181 std.mem.startsWith(u16, name_wide, &[_]u16{ '\\', 'c', 'y', 'g', 'w', 'i', 'n', '-' })) and
182 std.mem.find(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null;
183}
184
185/// Returns whether or not ANSI escape codes will be treated as such,
186/// and attempts to enable support for ANSI escape codes if necessary
187/// (on Windows).
188///
189/// Returns `true` if ANSI escape codes are supported or support was
190/// successfully enabled. Returns false if ANSI escape codes are not
191/// supported or support was unable to be enabled.
192///
193/// See also `supportsAnsiEscapeCodes`.
194pub fn getOrEnableAnsiEscapeSupport(self: File) bool {
195 if (builtin.os.tag == .windows) {
196 var original_console_mode: windows.DWORD = 0;
197
198 // For Windows Terminal, VT Sequences processing is enabled by default.
199 if (windows.kernel32.GetConsoleMode(self.handle, &original_console_mode) != 0) {
200 if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
201
202 // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default.
203 // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/
204 //
205 // Note: In Microsoft's example for enabling virtual terminal processing, it
206 // shows attempting to enable `DISABLE_NEWLINE_AUTO_RETURN` as well:
207 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
208 // This is avoided because in the old Windows Console, that flag causes \n (as opposed to \r\n)
209 // to behave unexpectedly (the cursor moves down 1 row but remains on the same column).
210 // Additionally, the default console mode in Windows Terminal does not have
211 // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING`
212 // we end up matching the mode of Windows Terminal.
213 const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
214 const console_mode = original_console_mode | requested_console_modes;
215 if (windows.kernel32.SetConsoleMode(self.handle, console_mode) != 0) return true;
216 }
217
218 return self.isCygwinPty();
219 }
220 return self.supportsAnsiEscapeCodes();
221}
222
223/// Test whether ANSI escape codes will be treated as such without
224/// attempting to enable support for ANSI escape codes.
225///
226/// See also `getOrEnableAnsiEscapeSupport`.
227pub fn supportsAnsiEscapeCodes(self: File) bool {
228 if (builtin.os.tag == .windows) {
229 var console_mode: windows.DWORD = 0;
230 if (windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) {
231 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
232 }
233
234 return self.isCygwinPty();
235 }
236 if (builtin.os.tag == .wasi) {
237 // WASI sanitizes stdout when fd is a tty so ANSI escape codes
238 // will not be interpreted as actual cursor commands, and
239 // stderr is always sanitized.
240 return false;
241 }
242 if (self.isTty()) {
243 if (self.handle == posix.STDOUT_FILENO or self.handle == posix.STDERR_FILENO) {
244 if (posix.getenvZ("TERM")) |term| {
245 if (std.mem.eql(u8, term, "dumb"))
246 return false;
247 }
248 }
249 return true;
250 }
251 return false;
252}
253
254pub const SetEndPosError = posix.TruncateError;
255
256/// Shrinks or expands the file.
257/// The file offset after this call is left unchanged.
258pub fn setEndPos(self: File, length: u64) SetEndPosError!void {
259 try posix.ftruncate(self.handle, length);
260}
261
262pub const SeekError = posix.SeekError;
263
264/// Repositions read/write file offset relative to the current offset.
265/// TODO: integrate with async I/O
266pub fn seekBy(self: File, offset: i64) SeekError!void {
267 return posix.lseek_CUR(self.handle, offset);
268}
269
270/// Repositions read/write file offset relative to the end.
271/// TODO: integrate with async I/O
272pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
273 return posix.lseek_END(self.handle, offset);
274}
275
276/// Repositions read/write file offset relative to the beginning.
277/// TODO: integrate with async I/O
278pub fn seekTo(self: File, offset: u64) SeekError!void {
279 return posix.lseek_SET(self.handle, offset);
280}
281
282pub const GetSeekPosError = posix.SeekError || StatError;
283
284/// TODO: integrate with async I/O
285pub fn getPos(self: File) GetSeekPosError!u64 {
286 return posix.lseek_CUR_get(self.handle);
287}
288
289pub const GetEndPosError = std.os.windows.GetFileSizeError || StatError;
290
291/// TODO: integrate with async I/O
292pub fn getEndPos(self: File) GetEndPosError!u64 {
293 if (builtin.os.tag == .windows) {
294 return windows.GetFileSizeEx(self.handle);
295 }
296 return (try self.stat()).size;
297}
298
299pub const ModeError = StatError;
300
301/// TODO: integrate with async I/O
302pub fn mode(self: File) ModeError!Mode {
303 if (builtin.os.tag == .windows) {
304 return 0;
305 }
306 return (try self.stat()).mode;
307}
308
309pub const Stat = Io.File.Stat;
310
311pub const StatError = posix.FStatError;
312
313/// Returns `Stat` containing basic information about the `File`.
314pub fn stat(self: File) StatError!Stat {
315 var threaded: Io.Threaded = .init_single_threaded;
316 const io = threaded.ioBasic();
317 return Io.File.stat(.{ .handle = self.handle }, io);
318}
319
320pub const ChmodError = posix.FChmodError;
321
322/// Changes the mode of the file.
323/// The process must have the correct privileges in order to do this
324/// successfully, or must have the effective user ID matching the owner
325/// of the file.
326pub fn chmod(self: File, new_mode: Mode) ChmodError!void {
327 try posix.fchmod(self.handle, new_mode);
328}
329
330pub const ChownError = posix.FChownError;
331
332/// Changes the owner and group of the file.
333/// The process must have the correct privileges in order to do this
334/// successfully. The group may be changed by the owner of the file to
335/// any group of which the owner is a member. If the owner or group is
336/// specified as `null`, the ID is not changed.
337pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void {
338 try posix.fchown(self.handle, owner, group);
339}
340
341/// Cross-platform representation of permissions on a file.
342/// The `readonly` and `setReadonly` are the only methods available across all platforms.
343/// Platform-specific functionality is available through the `inner` field.
344pub const Permissions = struct {
345 /// You may use the `inner` field to use platform-specific functionality
346 inner: switch (builtin.os.tag) {
347 .windows => PermissionsWindows,
348 else => PermissionsUnix,
349 },
350
351 const Self = @This();
352
353 /// Returns `true` if permissions represent an unwritable file.
354 /// On Unix, `true` is returned only if no class has write permissions.
355 pub fn readOnly(self: Self) bool {
356 return self.inner.readOnly();
357 }
358
359 /// Sets whether write permissions are provided.
360 /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`.
361 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
362 pub fn setReadOnly(self: *Self, read_only: bool) void {
363 self.inner.setReadOnly(read_only);
364 }
365};
366
367pub const PermissionsWindows = struct {
368 attributes: windows.DWORD,
369
370 const Self = @This();
371
372 /// Returns `true` if permissions represent an unwritable file.
373 pub fn readOnly(self: Self) bool {
374 return self.attributes & windows.FILE_ATTRIBUTE_READONLY != 0;
375 }
376
377 /// Sets whether write permissions are provided.
378 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
379 pub fn setReadOnly(self: *Self, read_only: bool) void {
380 if (read_only) {
381 self.attributes |= windows.FILE_ATTRIBUTE_READONLY;
382 } else {
383 self.attributes &= ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY);
384 }
385 }
386};
387
388pub const PermissionsUnix = struct {
389 mode: Mode,
390
391 const Self = @This();
392
393 /// Returns `true` if permissions represent an unwritable file.
394 /// `true` is returned only if no class has write permissions.
395 pub fn readOnly(self: Self) bool {
396 return self.mode & 0o222 == 0;
397 }
398
399 /// Sets whether write permissions are provided.
400 /// This affects *all* classes. If this is undesired, use `unixSet`.
401 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
402 pub fn setReadOnly(self: *Self, read_only: bool) void {
403 if (read_only) {
404 self.mode &= ~@as(Mode, 0o222);
405 } else {
406 self.mode |= @as(Mode, 0o222);
407 }
408 }
409
410 pub const Class = enum(u2) {
411 user = 2,
412 group = 1,
413 other = 0,
414 };
415
416 pub const Permission = enum(u3) {
417 read = 0o4,
418 write = 0o2,
419 execute = 0o1,
420 };
421
422 /// Returns `true` if the chosen class has the selected permission.
423 /// This method is only available on Unix platforms.
424 pub fn unixHas(self: Self, class: Class, permission: Permission) bool {
425 const mask = @as(Mode, @intFromEnum(permission)) << @as(u3, @intFromEnum(class)) * 3;
426 return self.mode & mask != 0;
427 }
428
429 /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged.
430 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
431 pub fn unixSet(self: *Self, class: Class, permissions: struct {
432 read: ?bool = null,
433 write: ?bool = null,
434 execute: ?bool = null,
435 }) void {
436 const shift = @as(u3, @intFromEnum(class)) * 3;
437 if (permissions.read) |r| {
438 if (r) {
439 self.mode |= @as(Mode, 0o4) << shift;
440 } else {
441 self.mode &= ~(@as(Mode, 0o4) << shift);
442 }
443 }
444 if (permissions.write) |w| {
445 if (w) {
446 self.mode |= @as(Mode, 0o2) << shift;
447 } else {
448 self.mode &= ~(@as(Mode, 0o2) << shift);
449 }
450 }
451 if (permissions.execute) |x| {
452 if (x) {
453 self.mode |= @as(Mode, 0o1) << shift;
454 } else {
455 self.mode &= ~(@as(Mode, 0o1) << shift);
456 }
457 }
458 }
459
460 /// Returns a `Permissions` struct representing the permissions from the passed mode.
461 pub fn unixNew(new_mode: Mode) Self {
462 return Self{
463 .mode = new_mode,
464 };
465 }
466};
467
468pub const SetPermissionsError = ChmodError;
469
470/// Sets permissions according to the provided `Permissions` struct.
471/// This method is *NOT* available on WASI
472pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void {
473 switch (builtin.os.tag) {
474 .windows => {
475 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
476 var info = windows.FILE_BASIC_INFORMATION{
477 .CreationTime = 0,
478 .LastAccessTime = 0,
479 .LastWriteTime = 0,
480 .ChangeTime = 0,
481 .FileAttributes = permissions.inner.attributes,
482 };
483 const rc = windows.ntdll.NtSetInformationFile(
484 self.handle,
485 &io_status_block,
486 &info,
487 @sizeOf(windows.FILE_BASIC_INFORMATION),
488 .Basic,
489 );
490 switch (rc) {
491 .SUCCESS => return,
492 .INVALID_HANDLE => unreachable,
493 .ACCESS_DENIED => return error.AccessDenied,
494 else => return windows.unexpectedStatus(rc),
495 }
496 },
497 .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod
498 else => {
499 try self.chmod(permissions.inner.mode);
500 },
501 }
502}
503
504pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError;
505
506/// The underlying file system may have a different granularity than nanoseconds,
507/// and therefore this function cannot guarantee any precision will be stored.
508/// Further, the maximum value is limited by the system ABI. When a value is provided
509/// that exceeds this range, the value is clamped to the maximum.
510/// TODO: integrate with async I/O
511pub fn updateTimes(
512 self: File,
513 /// access timestamp in nanoseconds
514 atime: Io.Timestamp,
515 /// last modification timestamp in nanoseconds
516 mtime: Io.Timestamp,
517) UpdateTimesError!void {
518 if (builtin.os.tag == .windows) {
519 const atime_ft = windows.nanoSecondsToFileTime(atime);
520 const mtime_ft = windows.nanoSecondsToFileTime(mtime);
521 return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft);
522 }
523 const times = [2]posix.timespec{
524 posix.timespec{
525 .sec = math.cast(isize, @divFloor(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
526 .nsec = math.cast(isize, @mod(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
527 },
528 posix.timespec{
529 .sec = math.cast(isize, @divFloor(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
530 .nsec = math.cast(isize, @mod(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
531 },
532 };
533 try posix.futimens(self.handle, &times);
534}
535
536pub const ReadError = posix.ReadError;
537pub const PReadError = posix.PReadError;
538
539pub fn read(self: File, buffer: []u8) ReadError!usize {
540 if (is_windows) {
541 return windows.ReadFile(self.handle, buffer, null);
542 }
543
544 return posix.read(self.handle, buffer);
545}
546
547/// On Windows, this function currently does alter the file pointer.
548/// https://github.com/ziglang/zig/issues/12783
549pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
550 if (is_windows) {
551 return windows.ReadFile(self.handle, buffer, offset);
552 }
553
554 return posix.pread(self.handle, buffer, offset);
555}
556
557/// Deprecated in favor of `Reader`.
558pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize {
559 var index: usize = 0;
560 while (index != buffer.len) {
561 const amt = try self.pread(buffer[index..], offset + index);
562 if (amt == 0) break;
563 index += amt;
564 }
565 return index;
566}
567
568/// See https://github.com/ziglang/zig/issues/7699
569pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize {
570 if (is_windows) {
571 if (iovecs.len == 0) return 0;
572 const first = iovecs[0];
573 return windows.ReadFile(self.handle, first.base[0..first.len], null);
574 }
575
576 return posix.readv(self.handle, iovecs);
577}
578
579/// See https://github.com/ziglang/zig/issues/7699
580/// On Windows, this function currently does alter the file pointer.
581/// https://github.com/ziglang/zig/issues/12783
582pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!usize {
583 if (is_windows) {
584 if (iovecs.len == 0) return 0;
585 const first = iovecs[0];
586 return windows.ReadFile(self.handle, first.base[0..first.len], offset);
587 }
588
589 return posix.preadv(self.handle, iovecs, offset);
590}
591
592pub const WriteError = posix.WriteError;
593pub const PWriteError = posix.PWriteError;
594
595pub fn write(self: File, bytes: []const u8) WriteError!usize {
596 if (is_windows) {
597 return windows.WriteFile(self.handle, bytes, null);
598 }
599
600 return posix.write(self.handle, bytes);
601}
602
603pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
604 var index: usize = 0;
605 while (index < bytes.len) {
606 index += try self.write(bytes[index..]);
607 }
608}
609
610/// Deprecated in favor of `Writer`.
611pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
612 var index: usize = 0;
613 while (index < bytes.len) {
614 index += try self.pwrite(bytes[index..], offset + index);
615 }
616}
617
618/// On Windows, this function currently does alter the file pointer.
619/// https://github.com/ziglang/zig/issues/12783
620pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
621 if (is_windows) {
622 return windows.WriteFile(self.handle, bytes, offset);
623 }
624
625 return posix.pwrite(self.handle, bytes, offset);
626}
627
628/// See https://github.com/ziglang/zig/issues/7699
629pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
630 if (is_windows) {
631 // TODO improve this to use WriteFileScatter
632 if (iovecs.len == 0) return 0;
633 const first = iovecs[0];
634 return windows.WriteFile(self.handle, first.base[0..first.len], null);
635 }
636
637 return posix.writev(self.handle, iovecs);
638}
639
640/// See https://github.com/ziglang/zig/issues/7699
641/// On Windows, this function currently does alter the file pointer.
642/// https://github.com/ziglang/zig/issues/12783
643pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!usize {
644 if (is_windows) {
645 if (iovecs.len == 0) return 0;
646 const first = iovecs[0];
647 return windows.WriteFile(self.handle, first.base[0..first.len], offset);
648 }
649
650 return posix.pwritev(self.handle, iovecs, offset);
651}
652
653/// Deprecated in favor of `Writer`.
654pub const CopyRangeError = posix.CopyFileRangeError;
655
656/// Deprecated in favor of `Writer`.
657pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
658 const adjusted_len = math.cast(usize, len) orelse maxInt(usize);
659 const result = try posix.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0);
660 return result;
661}
662
663/// Deprecated in favor of `Writer`.
664pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
665 var total_bytes_copied: u64 = 0;
666 var in_off = in_offset;
667 var out_off = out_offset;
668 while (total_bytes_copied < len) {
669 const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied);
670 if (amt_copied == 0) return total_bytes_copied;
671 total_bytes_copied += amt_copied;
672 in_off += amt_copied;
673 out_off += amt_copied;
674 }
675 return total_bytes_copied;
676}
677
678/// Deprecated in favor of `Io.File.Reader`.
679pub const Reader = Io.File.Reader;
680
681pub const Writer = struct {
682 file: File,
683 err: ?WriteError = null,
684 mode: Writer.Mode = .positional,
685 /// Tracks the true seek position in the file. To obtain the logical
686 /// position, add the buffer size to this value.
687 pos: u64 = 0,
688 sendfile_err: ?SendfileError = null,
689 copy_file_range_err: ?CopyFileRangeError = null,
690 fcopyfile_err: ?FcopyfileError = null,
691 seek_err: ?Writer.SeekError = null,
692 interface: Io.Writer,
693
694 pub const Mode = Reader.Mode;
695
696 pub const SendfileError = error{
697 UnsupportedOperation,
698 SystemResources,
699 InputOutput,
700 BrokenPipe,
701 WouldBlock,
702 Unexpected,
703 };
704
705 pub const CopyFileRangeError = std.os.freebsd.CopyFileRangeError || std.os.linux.wrapped.CopyFileRangeError;
706
707 pub const FcopyfileError = error{
708 OperationNotSupported,
709 OutOfMemory,
710 Unexpected,
711 };
712
713 pub const SeekError = File.SeekError;
714
715 /// Number of slices to store on the stack, when trying to send as many byte
716 /// vectors through the underlying write calls as possible.
717 const max_buffers_len = 16;
718
719 pub fn init(file: File, buffer: []u8) Writer {
720 return .{
721 .file = file,
722 .interface = initInterface(buffer),
723 .mode = .positional,
724 };
725 }
726
727 /// Positional is more threadsafe, since the global seek position is not
728 /// affected, but when such syscalls are not available, preemptively
729 /// initializing in streaming mode will skip a failed syscall.
730 pub fn initStreaming(file: File, buffer: []u8) Writer {
731 return .{
732 .file = file,
733 .interface = initInterface(buffer),
734 .mode = .streaming,
735 };
736 }
737
738 pub fn initInterface(buffer: []u8) Io.Writer {
739 return .{
740 .vtable = &.{
741 .drain = drain,
742 .sendFile = sendFile,
743 },
744 .buffer = buffer,
745 };
746 }
747
748 /// TODO when this logic moves from fs.File to Io.File the io parameter should be deleted
749 pub fn moveToReader(w: *Writer, io: Io) Reader {
750 defer w.* = undefined;
751 return .{
752 .io = io,
753 .file = .{ .handle = w.file.handle },
754 .mode = w.mode,
755 .pos = w.pos,
756 .interface = Reader.initInterface(w.interface.buffer),
757 .seek_err = w.seek_err,
758 };
759 }
760
761 pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
762 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
763 const handle = w.file.handle;
764 const buffered = io_w.buffered();
765 if (is_windows) switch (w.mode) {
766 .positional, .positional_reading => {
767 if (buffered.len != 0) {
768 const n = windows.WriteFile(handle, buffered, w.pos) catch |err| {
769 w.err = err;
770 return error.WriteFailed;
771 };
772 w.pos += n;
773 return io_w.consume(n);
774 }
775 for (data[0 .. data.len - 1]) |buf| {
776 if (buf.len == 0) continue;
777 const n = windows.WriteFile(handle, buf, w.pos) catch |err| {
778 w.err = err;
779 return error.WriteFailed;
780 };
781 w.pos += n;
782 return io_w.consume(n);
783 }
784 const pattern = data[data.len - 1];
785 if (pattern.len == 0 or splat == 0) return 0;
786 const n = windows.WriteFile(handle, pattern, w.pos) catch |err| {
787 w.err = err;
788 return error.WriteFailed;
789 };
790 w.pos += n;
791 return io_w.consume(n);
792 },
793 .streaming, .streaming_reading => {
794 if (buffered.len != 0) {
795 const n = windows.WriteFile(handle, buffered, null) catch |err| {
796 w.err = err;
797 return error.WriteFailed;
798 };
799 w.pos += n;
800 return io_w.consume(n);
801 }
802 for (data[0 .. data.len - 1]) |buf| {
803 if (buf.len == 0) continue;
804 const n = windows.WriteFile(handle, buf, null) catch |err| {
805 w.err = err;
806 return error.WriteFailed;
807 };
808 w.pos += n;
809 return io_w.consume(n);
810 }
811 const pattern = data[data.len - 1];
812 if (pattern.len == 0 or splat == 0) return 0;
813 const n = windows.WriteFile(handle, pattern, null) catch |err| {
814 w.err = err;
815 return error.WriteFailed;
816 };
817 w.pos += n;
818 return io_w.consume(n);
819 },
820 .failure => return error.WriteFailed,
821 };
822 var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
823 var len: usize = 0;
824 if (buffered.len > 0) {
825 iovecs[len] = .{ .base = buffered.ptr, .len = buffered.len };
826 len += 1;
827 }
828 for (data[0 .. data.len - 1]) |d| {
829 if (d.len == 0) continue;
830 iovecs[len] = .{ .base = d.ptr, .len = d.len };
831 len += 1;
832 if (iovecs.len - len == 0) break;
833 }
834 const pattern = data[data.len - 1];
835 if (iovecs.len - len != 0) switch (splat) {
836 0 => {},
837 1 => if (pattern.len != 0) {
838 iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len };
839 len += 1;
840 },
841 else => switch (pattern.len) {
842 0 => {},
843 1 => {
844 const splat_buffer_candidate = io_w.buffer[io_w.end..];
845 var backup_buffer: [64]u8 = undefined;
846 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
847 splat_buffer_candidate
848 else
849 &backup_buffer;
850 const memset_len = @min(splat_buffer.len, splat);
851 const buf = splat_buffer[0..memset_len];
852 @memset(buf, pattern[0]);
853 iovecs[len] = .{ .base = buf.ptr, .len = buf.len };
854 len += 1;
855 var remaining_splat = splat - buf.len;
856 while (remaining_splat > splat_buffer.len and iovecs.len - len != 0) {
857 assert(buf.len == splat_buffer.len);
858 iovecs[len] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len };
859 len += 1;
860 remaining_splat -= splat_buffer.len;
861 }
862 if (remaining_splat > 0 and iovecs.len - len != 0) {
863 iovecs[len] = .{ .base = splat_buffer.ptr, .len = remaining_splat };
864 len += 1;
865 }
866 },
867 else => for (0..splat) |_| {
868 iovecs[len] = .{ .base = pattern.ptr, .len = pattern.len };
869 len += 1;
870 if (iovecs.len - len == 0) break;
871 },
872 },
873 };
874 if (len == 0) return 0;
875 switch (w.mode) {
876 .positional, .positional_reading => {
877 const n = std.posix.pwritev(handle, iovecs[0..len], w.pos) catch |err| switch (err) {
878 error.Unseekable => {
879 w.mode = w.mode.toStreaming();
880 const pos = w.pos;
881 if (pos != 0) {
882 w.pos = 0;
883 w.seekTo(@intCast(pos)) catch {
884 w.mode = .failure;
885 return error.WriteFailed;
886 };
887 }
888 return 0;
889 },
890 else => |e| {
891 w.err = e;
892 return error.WriteFailed;
893 },
894 };
895 w.pos += n;
896 return io_w.consume(n);
897 },
898 .streaming, .streaming_reading => {
899 const n = std.posix.writev(handle, iovecs[0..len]) catch |err| {
900 w.err = err;
901 return error.WriteFailed;
902 };
903 w.pos += n;
904 return io_w.consume(n);
905 },
906 .failure => return error.WriteFailed,
907 }
908 }
909
910 pub fn sendFile(
911 io_w: *Io.Writer,
912 file_reader: *Io.File.Reader,
913 limit: Io.Limit,
914 ) Io.Writer.FileError!usize {
915 const reader_buffered = file_reader.interface.buffered();
916 if (reader_buffered.len >= @intFromEnum(limit))
917 return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered));
918 const writer_buffered = io_w.buffered();
919 const file_limit = @intFromEnum(limit) - reader_buffered.len;
920 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
921 const out_fd = w.file.handle;
922 const in_fd = file_reader.file.handle;
923
924 if (file_reader.size) |size| {
925 if (size - file_reader.pos == 0) {
926 if (reader_buffered.len != 0) {
927 return sendFileBuffered(io_w, file_reader, reader_buffered);
928 } else {
929 return error.EndOfStream;
930 }
931 }
932 }
933
934 if (native_os == .freebsd and w.mode == .streaming) sf: {
935 // Try using sendfile on FreeBSD.
936 if (w.sendfile_err != null) break :sf;
937 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
938 var hdtr_data: std.c.sf_hdtr = undefined;
939 var headers: [2]posix.iovec_const = undefined;
940 var headers_i: u8 = 0;
941 if (writer_buffered.len != 0) {
942 headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len };
943 headers_i += 1;
944 }
945 if (reader_buffered.len != 0) {
946 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
947 headers_i += 1;
948 }
949 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
950 hdtr_data = .{
951 .headers = &headers,
952 .hdr_cnt = headers_i,
953 .trailers = null,
954 .trl_cnt = 0,
955 };
956 break :b &hdtr_data;
957 };
958 var sbytes: std.c.off_t = undefined;
959 const nbytes: usize = @min(file_limit, maxInt(usize));
960 const flags = 0;
961 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) {
962 .SUCCESS, .INTR => {},
963 .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation,
964 .BADF => if (builtin.mode == .Debug) @panic("race condition") else {
965 w.sendfile_err = error.Unexpected;
966 },
967 .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else {
968 w.sendfile_err = error.Unexpected;
969 },
970 .NOTCONN => w.sendfile_err = error.BrokenPipe,
971 .AGAIN, .BUSY => if (sbytes == 0) {
972 w.sendfile_err = error.WouldBlock;
973 },
974 .IO => w.sendfile_err = error.InputOutput,
975 .PIPE => w.sendfile_err = error.BrokenPipe,
976 .NOBUFS => w.sendfile_err = error.SystemResources,
977 else => |err| w.sendfile_err = posix.unexpectedErrno(err),
978 }
979 if (w.sendfile_err != null) {
980 // Give calling code chance to observe the error before trying
981 // something else.
982 return 0;
983 }
984 if (sbytes == 0) {
985 file_reader.size = file_reader.pos;
986 return error.EndOfStream;
987 }
988 const consumed = io_w.consume(@intCast(sbytes));
989 file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
990 return consumed;
991 }
992
993 if (native_os.isDarwin() and w.mode == .streaming) sf: {
994 // Try using sendfile on macOS.
995 if (w.sendfile_err != null) break :sf;
996 const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
997 var hdtr_data: std.c.sf_hdtr = undefined;
998 var headers: [2]posix.iovec_const = undefined;
999 var headers_i: u8 = 0;
1000 if (writer_buffered.len != 0) {
1001 headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len };
1002 headers_i += 1;
1003 }
1004 if (reader_buffered.len != 0) {
1005 headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
1006 headers_i += 1;
1007 }
1008 const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
1009 hdtr_data = .{
1010 .headers = &headers,
1011 .hdr_cnt = headers_i,
1012 .trailers = null,
1013 .trl_cnt = 0,
1014 };
1015 break :b &hdtr_data;
1016 };
1017 const max_count = maxInt(i32); // Avoid EINVAL.
1018 var len: std.c.off_t = @min(file_limit, max_count);
1019 const flags = 0;
1020 switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) {
1021 .SUCCESS, .INTR => {},
1022 .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation,
1023 .BADF => if (builtin.mode == .Debug) @panic("race condition") else {
1024 w.sendfile_err = error.Unexpected;
1025 },
1026 .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else {
1027 w.sendfile_err = error.Unexpected;
1028 },
1029 .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else {
1030 w.sendfile_err = error.Unexpected;
1031 },
1032 .NOTCONN => w.sendfile_err = error.BrokenPipe,
1033 .AGAIN => if (len == 0) {
1034 w.sendfile_err = error.WouldBlock;
1035 },
1036 .IO => w.sendfile_err = error.InputOutput,
1037 .PIPE => w.sendfile_err = error.BrokenPipe,
1038 else => |err| w.sendfile_err = posix.unexpectedErrno(err),
1039 }
1040 if (w.sendfile_err != null) {
1041 // Give calling code chance to observe the error before trying
1042 // something else.
1043 return 0;
1044 }
1045 if (len == 0) {
1046 file_reader.size = file_reader.pos;
1047 return error.EndOfStream;
1048 }
1049 const consumed = io_w.consume(@bitCast(len));
1050 file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
1051 return consumed;
1052 }
1053
1054 if (native_os == .linux and w.mode == .streaming) sf: {
1055 // Try using sendfile on Linux.
1056 if (w.sendfile_err != null) break :sf;
1057 // Linux sendfile does not support headers.
1058 if (writer_buffered.len != 0 or reader_buffered.len != 0)
1059 return sendFileBuffered(io_w, file_reader, reader_buffered);
1060 const max_count = 0x7ffff000; // Avoid EINVAL.
1061 var off: std.os.linux.off_t = undefined;
1062 const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) {
1063 .positional => o: {
1064 const size = file_reader.getSize() catch return 0;
1065 off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed;
1066 break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) };
1067 },
1068 .streaming => .{ null, limit.minInt(max_count) },
1069 .streaming_reading, .positional_reading => break :sf,
1070 .failure => return error.ReadFailed,
1071 };
1072 const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) {
1073 error.Unseekable => {
1074 file_reader.mode = file_reader.mode.toStreaming();
1075 const pos = file_reader.pos;
1076 if (pos != 0) {
1077 file_reader.pos = 0;
1078 file_reader.seekBy(@intCast(pos)) catch {
1079 file_reader.mode = .failure;
1080 return error.ReadFailed;
1081 };
1082 }
1083 return 0;
1084 },
1085 else => |e| {
1086 w.sendfile_err = e;
1087 return 0;
1088 },
1089 };
1090 if (n == 0) {
1091 file_reader.size = file_reader.pos;
1092 return error.EndOfStream;
1093 }
1094 file_reader.pos += n;
1095 w.pos += n;
1096 return n;
1097 }
1098
1099 const copy_file_range = switch (native_os) {
1100 .freebsd => std.os.freebsd.copy_file_range,
1101 .linux => std.os.linux.wrapped.copy_file_range,
1102 else => {},
1103 };
1104 if (@TypeOf(copy_file_range) != void) cfr: {
1105 if (w.copy_file_range_err != null) break :cfr;
1106 if (writer_buffered.len != 0 or reader_buffered.len != 0)
1107 return sendFileBuffered(io_w, file_reader, reader_buffered);
1108 var off_in: i64 = undefined;
1109 var off_out: i64 = undefined;
1110 const off_in_ptr: ?*i64 = switch (file_reader.mode) {
1111 .positional_reading, .streaming_reading => return error.Unimplemented,
1112 .positional => p: {
1113 off_in = @intCast(file_reader.pos);
1114 break :p &off_in;
1115 },
1116 .streaming => null,
1117 .failure => return error.WriteFailed,
1118 };
1119 const off_out_ptr: ?*i64 = switch (w.mode) {
1120 .positional_reading, .streaming_reading => return error.Unimplemented,
1121 .positional => p: {
1122 off_out = @intCast(w.pos);
1123 break :p &off_out;
1124 },
1125 .streaming => null,
1126 .failure => return error.WriteFailed,
1127 };
1128 const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| {
1129 w.copy_file_range_err = err;
1130 return 0;
1131 };
1132 if (n == 0) {
1133 file_reader.size = file_reader.pos;
1134 return error.EndOfStream;
1135 }
1136 file_reader.pos += n;
1137 w.pos += n;
1138 return n;
1139 }
1140
1141 if (builtin.os.tag.isDarwin()) fcf: {
1142 if (w.fcopyfile_err != null) break :fcf;
1143 if (file_reader.pos != 0) break :fcf;
1144 if (w.pos != 0) break :fcf;
1145 if (limit != .unlimited) break :fcf;
1146 const size = file_reader.getSize() catch break :fcf;
1147 if (writer_buffered.len != 0 or reader_buffered.len != 0)
1148 return sendFileBuffered(io_w, file_reader, reader_buffered);
1149 const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true });
1150 switch (posix.errno(rc)) {
1151 .SUCCESS => {},
1152 .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else {
1153 w.fcopyfile_err = error.Unexpected;
1154 return 0;
1155 },
1156 .NOMEM => {
1157 w.fcopyfile_err = error.OutOfMemory;
1158 return 0;
1159 },
1160 .OPNOTSUPP => {
1161 w.fcopyfile_err = error.OperationNotSupported;
1162 return 0;
1163 },
1164 else => |err| {
1165 w.fcopyfile_err = posix.unexpectedErrno(err);
1166 return 0;
1167 },
1168 }
1169 file_reader.pos = size;
1170 w.pos = size;
1171 return size;
1172 }
1173
1174 return error.Unimplemented;
1175 }
1176
1177 fn sendFileBuffered(
1178 io_w: *Io.Writer,
1179 file_reader: *Io.File.Reader,
1180 reader_buffered: []const u8,
1181 ) Io.Writer.FileError!usize {
1182 const n = try drain(io_w, &.{reader_buffered}, 1);
1183 file_reader.seekBy(@intCast(n)) catch return error.ReadFailed;
1184 return n;
1185 }
1186
1187 pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void {
1188 try w.interface.flush();
1189 try seekToUnbuffered(w, offset);
1190 }
1191
1192 /// Asserts that no data is currently buffered.
1193 pub fn seekToUnbuffered(w: *Writer, offset: u64) Writer.SeekError!void {
1194 assert(w.interface.buffered().len == 0);
1195 switch (w.mode) {
1196 .positional, .positional_reading => {
1197 w.pos = offset;
1198 },
1199 .streaming, .streaming_reading => {
1200 if (w.seek_err) |err| return err;
1201 posix.lseek_SET(w.file.handle, offset) catch |err| {
1202 w.seek_err = err;
1203 return err;
1204 };
1205 w.pos = offset;
1206 },
1207 .failure => return w.seek_err.?,
1208 }
1209 }
1210
1211 pub const EndError = SetEndPosError || Io.Writer.Error;
1212
1213 /// Flushes any buffered data and sets the end position of the file.
1214 ///
1215 /// If not overwriting existing contents, then calling `interface.flush`
1216 /// directly is sufficient.
1217 ///
1218 /// Flush failure is handled by setting `err` so that it can be handled
1219 /// along with other write failures.
1220 pub fn end(w: *Writer) EndError!void {
1221 try w.interface.flush();
1222 switch (w.mode) {
1223 .positional,
1224 .positional_reading,
1225 => w.file.setEndPos(w.pos) catch |err| switch (err) {
1226 error.NonResizable => return,
1227 else => |e| return e,
1228 },
1229
1230 .streaming,
1231 .streaming_reading,
1232 .failure,
1233 => {},
1234 }
1235 }
1236};
1237
1238/// Defaults to positional reading; falls back to streaming.
1239///
1240/// Positional is more threadsafe, since the global seek position is not
1241/// affected.
1242pub fn reader(file: File, io: Io, buffer: []u8) Reader {
1243 return .init(.{ .handle = file.handle }, io, buffer);
1244}
1245
1246/// Positional is more threadsafe, since the global seek position is not
1247/// affected, but when such syscalls are not available, preemptively
1248/// initializing in streaming mode skips a failed syscall.
1249pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
1250 return .initStreaming(.{ .handle = file.handle }, io, buffer);
1251}
1252
1253/// Defaults to positional reading; falls back to streaming.
1254///
1255/// Positional is more threadsafe, since the global seek position is not
1256/// affected.
1257pub fn writer(file: File, buffer: []u8) Writer {
1258 return .init(file, buffer);
1259}
1260
1261/// Positional is more threadsafe, since the global seek position is not
1262/// affected, but when such syscalls are not available, preemptively
1263/// initializing in streaming mode will skip a failed syscall.
1264pub fn writerStreaming(file: File, buffer: []u8) Writer {
1265 return .initStreaming(file, buffer);
1266}
1267
1268const range_off: windows.LARGE_INTEGER = 0;
1269const range_len: windows.LARGE_INTEGER = 1;
1270
1271pub const LockError = error{
1272 SystemResources,
1273 FileLocksNotSupported,
1274} || posix.UnexpectedError;
1275
1276/// Blocks when an incompatible lock is held by another process.
1277/// A process may hold only one type of lock (shared or exclusive) on
1278/// a file. When a process terminates in any way, the lock is released.
1279///
1280/// Assumes the file is unlocked.
1281///
1282/// TODO: integrate with async I/O
1283pub fn lock(file: File, l: Lock) LockError!void {
1284 if (is_windows) {
1285 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1286 const exclusive = switch (l) {
1287 .none => return,
1288 .shared => false,
1289 .exclusive => true,
1290 };
1291 return windows.LockFile(
1292 file.handle,
1293 null,
1294 null,
1295 null,
1296 &io_status_block,
1297 &range_off,
1298 &range_len,
1299 null,
1300 windows.FALSE, // non-blocking=false
1301 @intFromBool(exclusive),
1302 ) catch |err| switch (err) {
1303 error.WouldBlock => unreachable, // non-blocking=false
1304 else => |e| return e,
1305 };
1306 } else {
1307 return posix.flock(file.handle, switch (l) {
1308 .none => posix.LOCK.UN,
1309 .shared => posix.LOCK.SH,
1310 .exclusive => posix.LOCK.EX,
1311 }) catch |err| switch (err) {
1312 error.WouldBlock => unreachable, // non-blocking=false
1313 else => |e| return e,
1314 };
1315 }
1316}
1317
1318/// Assumes the file is locked.
1319pub fn unlock(file: File) void {
1320 if (is_windows) {
1321 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1322 return windows.UnlockFile(
1323 file.handle,
1324 &io_status_block,
1325 &range_off,
1326 &range_len,
1327 0,
1328 ) catch |err| switch (err) {
1329 error.RangeNotLocked => unreachable, // Function assumes unlocked.
1330 error.Unexpected => unreachable, // Resource deallocation must succeed.
1331 };
1332 } else {
1333 return posix.flock(file.handle, posix.LOCK.UN) catch |err| switch (err) {
1334 error.WouldBlock => unreachable, // unlocking can't block
1335 error.SystemResources => unreachable, // We are deallocating resources.
1336 error.FileLocksNotSupported => unreachable, // We already got the lock.
1337 error.Unexpected => unreachable, // Resource deallocation must succeed.
1338 };
1339 }
1340}
1341
1342/// Attempts to obtain a lock, returning `true` if the lock is
1343/// obtained, and `false` if there was an existing incompatible lock held.
1344/// A process may hold only one type of lock (shared or exclusive) on
1345/// a file. When a process terminates in any way, the lock is released.
1346///
1347/// Assumes the file is unlocked.
1348///
1349/// TODO: integrate with async I/O
1350pub fn tryLock(file: File, l: Lock) LockError!bool {
1351 if (is_windows) {
1352 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1353 const exclusive = switch (l) {
1354 .none => return,
1355 .shared => false,
1356 .exclusive => true,
1357 };
1358 windows.LockFile(
1359 file.handle,
1360 null,
1361 null,
1362 null,
1363 &io_status_block,
1364 &range_off,
1365 &range_len,
1366 null,
1367 windows.TRUE, // non-blocking=true
1368 @intFromBool(exclusive),
1369 ) catch |err| switch (err) {
1370 error.WouldBlock => return false,
1371 else => |e| return e,
1372 };
1373 } else {
1374 posix.flock(file.handle, switch (l) {
1375 .none => posix.LOCK.UN,
1376 .shared => posix.LOCK.SH | posix.LOCK.NB,
1377 .exclusive => posix.LOCK.EX | posix.LOCK.NB,
1378 }) catch |err| switch (err) {
1379 error.WouldBlock => return false,
1380 else => |e| return e,
1381 };
1382 }
1383 return true;
1384}
1385
1386/// Assumes the file is already locked in exclusive mode.
1387/// Atomically modifies the lock to be in shared mode, without releasing it.
1388///
1389/// TODO: integrate with async I/O
1390pub fn downgradeLock(file: File) LockError!void {
1391 if (is_windows) {
1392 // On Windows it works like a semaphore + exclusivity flag. To implement this
1393 // function, we first obtain another lock in shared mode. This changes the
1394 // exclusivity flag, but increments the semaphore to 2. So we follow up with
1395 // an NtUnlockFile which decrements the semaphore but does not modify the
1396 // exclusivity flag.
1397 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1398 windows.LockFile(
1399 file.handle,
1400 null,
1401 null,
1402 null,
1403 &io_status_block,
1404 &range_off,
1405 &range_len,
1406 null,
1407 windows.TRUE, // non-blocking=true
1408 windows.FALSE, // exclusive=false
1409 ) catch |err| switch (err) {
1410 error.WouldBlock => unreachable, // File was not locked in exclusive mode.
1411 else => |e| return e,
1412 };
1413 return windows.UnlockFile(
1414 file.handle,
1415 &io_status_block,
1416 &range_off,
1417 &range_len,
1418 0,
1419 ) catch |err| switch (err) {
1420 error.RangeNotLocked => unreachable, // File was not locked.
1421 error.Unexpected => unreachable, // Resource deallocation must succeed.
1422 };
1423 } else {
1424 return posix.flock(file.handle, posix.LOCK.SH | posix.LOCK.NB) catch |err| switch (err) {
1425 error.WouldBlock => unreachable, // File was not locked in exclusive mode.
1426 else => |e| return e,
1427 };
1428 }
1429}
1430
1431pub fn adaptToNewApi(file: File) Io.File {
1432 return .{ .handle = file.handle };
1433}
1434
1435pub fn adaptFromNewApi(file: Io.File) File {
1436 return .{ .handle = file.handle };
1437}
lib/std/fs/path.zig+2-2
......@@ -872,7 +872,7 @@ pub fn resolve(allocator: Allocator, paths: []const []const u8) Allocator.Error!
872872
873873/// This function is like a series of `cd` statements executed one after another.
874874/// It resolves "." and ".." to the best of its ability, but will not convert relative paths to
875/// an absolute path, use std.fs.Dir.realpath instead.
875/// an absolute path, use Io.Dir.realpath instead.
876876/// ".." components may persist in the resolved path if the resolved path is relative or drive-relative.
877877/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.
878878///
......@@ -1095,7 +1095,7 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator
10951095
10961096/// This function is like a series of `cd` statements executed one after another.
10971097/// It resolves "." and ".." to the best of its ability, but will not convert relative paths to
1098/// an absolute path, use std.fs.Dir.realpath instead.
1098/// an absolute path, use Io.Dir.realpath instead.
10991099/// ".." components may persist in the resolved path if the resolved path is relative.
11001100/// The result does not have a trailing path separator.
11011101/// This function does not perform any syscalls. Executing this series of path
lib/std/fs/test.zig+1164-844
......@@ -3,66 +3,89 @@ const native_os = builtin.os.tag;
33
44const std = @import("../std.zig");
55const Io = std.Io;
6const testing = std.testing;
7const fs = std.fs;
86const mem = std.mem;
7const Allocator = std.mem.Allocator;
98const wasi = std.os.wasi;
109const windows = std.os.windows;
11const posix = std.posix;
12
1310const ArenaAllocator = std.heap.ArenaAllocator;
14const Dir = std.fs.Dir;
15const File = std.fs.File;
16const tmpDir = testing.tmpDir;
17const SymLinkFlags = std.fs.Dir.SymLinkFlags;
11const Dir = std.Io.Dir;
12const File = std.Io.File;
13const SymLinkFlags = std.Io.Dir.SymLinkFlags;
14
15const testing = std.testing;
16const expect = std.testing.expect;
17const expectEqual = std.testing.expectEqual;
18const expectEqualSlices = std.testing.expectEqualSlices;
19const expectEqualStrings = std.testing.expectEqualStrings;
20const expectError = std.testing.expectError;
21const tmpDir = std.testing.tmpDir;
1822
1923const PathType = enum {
2024 relative,
2125 absolute,
2226 unc,
2327
24 pub fn isSupported(self: PathType, target_os: std.Target.Os) bool {
28 fn isSupported(self: PathType, target_os: std.Target.Os) bool {
2529 return switch (self) {
2630 .relative => true,
27 .absolute => std.os.isGetFdPathSupportedOnTarget(target_os),
31 .absolute => switch (target_os.tag) {
32 .windows,
33 .driverkit,
34 .ios,
35 .maccatalyst,
36 .macos,
37 .tvos,
38 .visionos,
39 .watchos,
40 .linux,
41 .illumos,
42 .freebsd,
43 .serenity,
44 => true,
45
46 .dragonfly => target_os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
47 .netbsd => target_os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
48 else => false,
49 },
2850 .unc => target_os.tag == .windows,
2951 };
3052 }
3153
32 pub const TransformError = posix.RealPathError || error{OutOfMemory};
33 pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8;
54 const TransformError = Dir.RealPathError || error{OutOfMemory};
55 const TransformFn = fn (Allocator, Io, Dir, relative_path: [:0]const u8) TransformError![:0]const u8;
3456
35 pub fn getTransformFn(comptime path_type: PathType) TransformFn {
57 fn getTransformFn(comptime path_type: PathType) TransformFn {
3658 switch (path_type) {
3759 .relative => return struct {
38 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
60 fn transform(allocator: Allocator, io: Io, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
3961 _ = allocator;
62 _ = io;
4063 _ = dir;
4164 return relative_path;
4265 }
4366 }.transform,
4467 .absolute => return struct {
45 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
68 fn transform(allocator: Allocator, io: Io, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
4669 // The final path may not actually exist which would cause realpath to fail.
4770 // So instead, we get the path of the dir and join it with the relative path.
48 var fd_path_buf: [fs.max_path_bytes]u8 = undefined;
49 const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf);
50 return fs.path.joinZ(allocator, &.{ dir_path, relative_path });
71 var fd_path_buf: [Dir.max_path_bytes]u8 = undefined;
72 const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)];
73 return Dir.path.joinZ(allocator, &.{ dir_path, relative_path });
5174 }
5275 }.transform,
5376 .unc => return struct {
54 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
77 fn transform(allocator: Allocator, io: Io, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
5578 // Any drive absolute path (C:\foo) can be converted into a UNC path by
5679 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
57 var fd_path_buf: [fs.max_path_bytes]u8 = undefined;
58 const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf);
80 var fd_path_buf: [Dir.max_path_bytes]u8 = undefined;
81 const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)];
5982 const windows_path_type = windows.getWin32PathType(u8, dir_path);
6083 switch (windows_path_type) {
61 .unc_absolute => return fs.path.joinZ(allocator, &.{ dir_path, relative_path }),
84 .unc_absolute => return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }),
6285 .drive_absolute => {
6386 // `C:\<...>` -> `\\127.0.0.1\C$\<...>`
6487 const prepended = "\\\\127.0.0.1\\";
65 var path = try fs.path.joinZ(allocator, &.{ prepended, dir_path, relative_path });
88 var path = try Dir.path.joinZ(allocator, &.{ prepended, dir_path, relative_path });
6689 path[prepended.len + 1] = '$';
6790 return path;
6891 },
......@@ -80,10 +103,10 @@ const TestContext = struct {
80103 path_sep: u8,
81104 arena: ArenaAllocator,
82105 tmp: testing.TmpDir,
83 dir: std.fs.Dir,
106 dir: Dir,
84107 transform_fn: *const PathType.TransformFn,
85108
86 pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
109 pub fn init(path_type: PathType, path_sep: u8, allocator: Allocator, transform_fn: *const PathType.TransformFn) TestContext {
87110 const tmp = tmpDir(.{ .iterate = true });
88111 return .{
89112 .io = testing.io,
......@@ -107,7 +130,7 @@ const TestContext = struct {
107130 /// `TestContext.deinit`.
108131 pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 {
109132 const allocator = self.arena.allocator();
110 const transformed_path = try self.transform_fn(allocator, self.dir, relative_path);
133 const transformed_path = try self.transform_fn(allocator, self.io, self.dir, relative_path);
111134 if (native_os == .windows) {
112135 const transformed_sep_path = try allocator.dupeZ(u8, transformed_path);
113136 std.mem.replaceScalar(u8, transformed_sep_path, switch (self.path_sep) {
......@@ -150,7 +173,7 @@ fn testWithAllSupportedPathTypes(test_func: anytype) !void {
150173
151174fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: u8, test_func: anytype) !void {
152175 if (!(comptime path_type.isSupported(builtin.os))) return;
153 if (!(comptime fs.path.isSep(path_sep))) return;
176 if (!(comptime Dir.path.isSep(path_sep))) return;
154177
155178 var ctx = TestContext.init(path_type, path_sep, testing.allocator, path_type.getTransformFn());
156179 defer ctx.deinit();
......@@ -160,8 +183,8 @@ fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep:
160183
161184// For use in test setup. If the symlink creation fails on Windows with
162185// AccessDenied, then make the test failure silent (it is not a Zig failure).
163fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void {
164 return dir.symLink(target, link, flags) catch |err| switch (err) {
186fn setupSymlink(io: Io, dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void {
187 return dir.symLink(io, target, link, flags) catch |err| switch (err) {
165188 // Symlink requires admin privileges on windows, so this test can legitimately fail.
166189 error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err,
167190 else => return err,
......@@ -170,50 +193,43 @@ fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFl
170193
171194// For use in test setup. If the symlink creation fails on Windows with
172195// AccessDenied, then make the test failure silent (it is not a Zig failure).
173fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlags) !void {
174 return fs.symLinkAbsolute(target, link, flags) catch |err| switch (err) {
196fn setupSymlinkAbsolute(io: Io, target: []const u8, link: []const u8, flags: SymLinkFlags) !void {
197 return Dir.symLinkAbsolute(io, target, link, flags) catch |err| switch (err) {
175198 error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err,
176199 else => return err,
177200 };
178201}
179202
180203test "Dir.readLink" {
204 const io = testing.io;
205
181206 try testWithAllSupportedPathTypes(struct {
182207 fn impl(ctx: *TestContext) !void {
183208 // Create some targets
184209 const file_target_path = try ctx.transformPath("file.txt");
185 try ctx.dir.writeFile(.{ .sub_path = file_target_path, .data = "nonsense" });
210 try ctx.dir.writeFile(io, .{ .sub_path = file_target_path, .data = "nonsense" });
186211 const dir_target_path = try ctx.transformPath("subdir");
187 try ctx.dir.makeDir(dir_target_path);
212 try ctx.dir.createDir(io, dir_target_path, .default_dir);
188213
189214 // On Windows, symlink targets always use the canonical path separator
190215 const canonical_file_target_path = try ctx.toCanonicalPathSep(file_target_path);
191216 const canonical_dir_target_path = try ctx.toCanonicalPathSep(dir_target_path);
192217
193218 // test 1: symlink to a file
194 try setupSymlink(ctx.dir, file_target_path, "symlink1", .{});
195 try testReadLink(ctx.dir, canonical_file_target_path, "symlink1");
196 if (builtin.os.tag == .windows) {
197 try testReadLinkW(testing.allocator, ctx.dir, canonical_file_target_path, "symlink1");
198 }
219 try setupSymlink(io, ctx.dir, file_target_path, "symlink1", .{});
220 try testReadLink(io, ctx.dir, canonical_file_target_path, "symlink1");
199221
200222 // test 2: symlink to a directory (can be different on Windows)
201 try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true });
202 try testReadLink(ctx.dir, canonical_dir_target_path, "symlink2");
203 if (builtin.os.tag == .windows) {
204 try testReadLinkW(testing.allocator, ctx.dir, canonical_dir_target_path, "symlink2");
205 }
223 try setupSymlink(io, ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true });
224 try testReadLink(io, ctx.dir, canonical_dir_target_path, "symlink2");
206225
207226 // test 3: relative path symlink
208 const parent_file = ".." ++ fs.path.sep_str ++ "target.txt";
227 const parent_file = ".." ++ Dir.path.sep_str ++ "target.txt";
209228 const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file);
210 var subdir = try ctx.dir.makeOpenPath("subdir", .{});
211 defer subdir.close();
212 try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{});
213 try testReadLink(subdir, canonical_parent_file, "relative-link.txt");
214 if (builtin.os.tag == .windows) {
215 try testReadLinkW(testing.allocator, subdir, canonical_parent_file, "relative-link.txt");
216 }
229 var subdir = try ctx.dir.createDirPathOpen(io, "subdir", .{});
230 defer subdir.close(io);
231 try setupSymlink(io, subdir, canonical_parent_file, "relative-link.txt", .{});
232 try testReadLink(io, subdir, canonical_parent_file, "relative-link.txt");
217233 }
218234 }.impl);
219235}
......@@ -221,55 +237,39 @@ test "Dir.readLink" {
221237test "Dir.readLink on non-symlinks" {
222238 try testWithAllSupportedPathTypes(struct {
223239 fn impl(ctx: *TestContext) !void {
240 const io = ctx.io;
224241 const file_path = try ctx.transformPath("file.txt");
225 try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "nonsense" });
242 try ctx.dir.writeFile(io, .{ .sub_path = file_path, .data = "nonsense" });
226243 const dir_path = try ctx.transformPath("subdir");
227 try ctx.dir.makeDir(dir_path);
244 try ctx.dir.createDir(io, dir_path, .default_dir);
228245
229246 // file
230 var buffer: [fs.max_path_bytes]u8 = undefined;
231 try std.testing.expectError(error.NotLink, ctx.dir.readLink(file_path, &buffer));
232 if (builtin.os.tag == .windows) {
233 var file_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.fd, file_path);
234 try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(file_path_w.span(), &file_path_w.data));
235 }
247 var buffer: [Dir.max_path_bytes]u8 = undefined;
248 try std.testing.expectError(error.NotLink, ctx.dir.readLink(io, file_path, &buffer));
236249
237250 // dir
238 try std.testing.expectError(error.NotLink, ctx.dir.readLink(dir_path, &buffer));
239 if (builtin.os.tag == .windows) {
240 var dir_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.fd, dir_path);
241 try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(dir_path_w.span(), &dir_path_w.data));
242 }
251 try std.testing.expectError(error.NotLink, ctx.dir.readLink(io, dir_path, &buffer));
243252 }
244253 }.impl);
245254}
246255
247fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
248 var buffer: [fs.max_path_bytes]u8 = undefined;
249 const actual = try dir.readLink(symlink_path, buffer[0..]);
250 try testing.expectEqualStrings(target_path, actual);
251}
252
253fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
254 const target_path_w = try std.unicode.wtf8ToWtf16LeAlloc(allocator, target_path);
255 defer allocator.free(target_path_w);
256 // Calling the W functions directly requires the path to be NT-prefixed
257 const symlink_path_w = try std.os.windows.sliceToPrefixedFileW(dir.fd, symlink_path);
258 const wtf16_buffer = try allocator.alloc(u16, target_path_w.len);
259 defer allocator.free(wtf16_buffer);
260 const actual = try dir.readLinkW(symlink_path_w.span(), wtf16_buffer);
261 try testing.expectEqualSlices(u16, target_path_w, actual);
256fn testReadLink(io: Io, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
257 var buffer: [Dir.max_path_bytes]u8 = undefined;
258 const actual = buffer[0..try dir.readLink(io, symlink_path, &buffer)];
259 try expectEqualStrings(target_path, actual);
262260}
263261
264fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {
265 var buffer: [fs.max_path_bytes]u8 = undefined;
266 const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]);
267 try testing.expectEqualStrings(target_path, given);
262fn testReadLinkAbsolute(io: Io, target_path: []const u8, symlink_path: []const u8) !void {
263 var buffer: [Dir.max_path_bytes]u8 = undefined;
264 const given = buffer[0..try Dir.readLinkAbsolute(io, symlink_path, &buffer)];
265 try expectEqualStrings(target_path, given);
268266}
269267
270268test "File.stat on a File that is a symlink returns Kind.sym_link" {
271 // This test requires getting a file descriptor of a symlink which
272 // is not possible on all targets
269 const io = testing.io;
270
271 // This test requires getting a file descriptor of a symlink which is not
272 // possible on all targets.
273273 switch (builtin.target.os.tag) {
274274 .windows, .linux => {},
275275 else => return error.SkipZigTest,
......@@ -278,99 +278,35 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" {
278278 try testWithAllSupportedPathTypes(struct {
279279 fn impl(ctx: *TestContext) !void {
280280 const dir_target_path = try ctx.transformPath("subdir");
281 try ctx.dir.makeDir(dir_target_path);
282
283 try setupSymlink(ctx.dir, dir_target_path, "symlink", .{ .is_directory = true });
284
285 var symlink: Dir = switch (builtin.target.os.tag) {
286 .windows => windows_symlink: {
287 const sub_path_w = try windows.cStrToPrefixedFileW(ctx.dir.fd, "symlink");
288
289 var handle: windows.HANDLE = undefined;
290
291 const path_len_bytes = @as(u16, @intCast(sub_path_w.span().len * 2));
292 var nt_name = windows.UNICODE_STRING{
293 .Length = path_len_bytes,
294 .MaximumLength = path_len_bytes,
295 .Buffer = @constCast(&sub_path_w.data),
296 };
297 var attr: windows.OBJECT_ATTRIBUTES = .{
298 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
299 .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else ctx.dir.fd,
300 .Attributes = .{},
301 .ObjectName = &nt_name,
302 .SecurityDescriptor = null,
303 .SecurityQualityOfService = null,
304 };
305 var io: windows.IO_STATUS_BLOCK = undefined;
306 const rc = windows.ntdll.NtCreateFile(
307 &handle,
308 .{
309 .SPECIFIC = .{ .FILE_DIRECTORY = .{
310 .READ_EA = true,
311 .TRAVERSE = true,
312 .READ_ATTRIBUTES = true,
313 } },
314 .STANDARD = .{
315 .RIGHTS = .READ,
316 .SYNCHRONIZE = true,
317 },
318 },
319 &attr,
320 &io,
321 null,
322 .{ .NORMAL = true },
323 .VALID_FLAGS,
324 .OPEN,
325 .{
326 .DIRECTORY_FILE = true,
327 .IO = .SYNCHRONOUS_NONALERT,
328 .OPEN_FOR_BACKUP_INTENT = true,
329 .OPEN_REPARSE_POINT = true, // the important thing here
330 },
331 null,
332 0,
333 );
281 try ctx.dir.createDir(io, dir_target_path, .default_dir);
334282
335 switch (rc) {
336 .SUCCESS => break :windows_symlink .{ .fd = handle },
337 else => return windows.unexpectedStatus(rc),
338 }
339 },
340 .linux => linux_symlink: {
341 const sub_path_c = try posix.toPosixPath("symlink");
342 // the O_NOFOLLOW | O_PATH combination can obtain a fd to a symlink
343 // note that if O_DIRECTORY is set, then this will error with ENOTDIR
344 const flags: posix.O = .{
345 .NOFOLLOW = true,
346 .PATH = true,
347 .ACCMODE = .RDONLY,
348 .CLOEXEC = true,
349 };
350 const fd = try posix.openatZ(ctx.dir.fd, &sub_path_c, flags, 0);
351 break :linux_symlink Dir{ .fd = fd };
352 },
353 else => unreachable,
354 };
355 defer symlink.close();
283 try setupSymlink(io, ctx.dir, dir_target_path, "symlink", .{ .is_directory = true });
284
285 var symlink: File = try ctx.dir.openFile(io, "symlink", .{
286 .follow_symlinks = false,
287 .path_only = true,
288 });
289 defer symlink.close(io);
356290
357 const stat = try symlink.stat();
358 try testing.expectEqual(File.Kind.sym_link, stat.kind);
291 const stat = try symlink.stat(io);
292 try expectEqual(File.Kind.sym_link, stat.kind);
359293 }
360294 }.impl);
361295}
362296
363297test "openDir" {
298 const io = testing.io;
299
364300 try testWithAllSupportedPathTypes(struct {
365301 fn impl(ctx: *TestContext) !void {
366302 const allocator = ctx.arena.allocator();
367303 const subdir_path = try ctx.transformPath("subdir");
368 try ctx.dir.makeDir(subdir_path);
304 try ctx.dir.createDir(io, subdir_path, .default_dir);
369305
370306 for ([_][]const u8{ "", ".", ".." }) |sub_path| {
371 const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path });
372 var dir = try ctx.dir.openDir(dir_path, .{});
373 defer dir.close();
307 const dir_path = try Dir.path.join(allocator, &.{ subdir_path, sub_path });
308 var dir = try ctx.dir.openDir(io, dir_path, .{});
309 defer dir.close(io);
374310 }
375311 }
376312 }.impl);
......@@ -380,79 +316,87 @@ test "accessAbsolute" {
380316 if (native_os == .wasi) return error.SkipZigTest;
381317 if (native_os == .openbsd) return error.SkipZigTest;
382318
319 const io = testing.io;
320 const gpa = testing.allocator;
321
383322 var tmp = tmpDir(.{});
384323 defer tmp.cleanup();
385324
386 const base_path = try tmp.dir.realpathAlloc(testing.allocator, ".");
387 defer testing.allocator.free(base_path);
325 const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
326 defer gpa.free(base_path);
388327
389 try fs.accessAbsolute(base_path, .{});
328 try Dir.accessAbsolute(io, base_path, .{});
390329}
391330
392331test "openDirAbsolute" {
393332 if (native_os == .wasi) return error.SkipZigTest;
394333 if (native_os == .openbsd) return error.SkipZigTest;
395334
335 const io = testing.io;
336 const gpa = testing.allocator;
337
396338 var tmp = tmpDir(.{});
397339 defer tmp.cleanup();
398340
399 const tmp_ino = (try tmp.dir.stat()).inode;
341 const tmp_ino = (try tmp.dir.stat(io)).inode;
400342
401 try tmp.dir.makeDir("subdir");
402 const sub_path = try tmp.dir.realpathAlloc(testing.allocator, "subdir");
403 defer testing.allocator.free(sub_path);
343 try tmp.dir.createDir(io, "subdir", .default_dir);
344 const sub_path = try tmp.dir.realPathFileAlloc(io, "subdir", gpa);
345 defer gpa.free(sub_path);
404346
405347 // Can open sub_path
406 var tmp_sub = try fs.openDirAbsolute(sub_path, .{});
407 defer tmp_sub.close();
348 var tmp_sub = try Dir.openDirAbsolute(io, sub_path, .{});
349 defer tmp_sub.close(io);
408350
409 const sub_ino = (try tmp_sub.stat()).inode;
351 const sub_ino = (try tmp_sub.stat(io)).inode;
410352
411353 {
412354 // Can open sub_path + ".."
413 const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".." });
355 const dir_path = try Dir.path.join(testing.allocator, &.{ sub_path, ".." });
414356 defer testing.allocator.free(dir_path);
415357
416 var dir = try fs.openDirAbsolute(dir_path, .{});
417 defer dir.close();
358 var dir = try Dir.openDirAbsolute(io, dir_path, .{});
359 defer dir.close(io);
418360
419 const ino = (try dir.stat()).inode;
420 try testing.expectEqual(tmp_ino, ino);
361 const ino = (try dir.stat(io)).inode;
362 try expectEqual(tmp_ino, ino);
421363 }
422364
423365 {
424366 // Can open sub_path + "."
425 const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, "." });
367 const dir_path = try Dir.path.join(testing.allocator, &.{ sub_path, "." });
426368 defer testing.allocator.free(dir_path);
427369
428 var dir = try fs.openDirAbsolute(dir_path, .{});
429 defer dir.close();
370 var dir = try Dir.openDirAbsolute(io, dir_path, .{});
371 defer dir.close(io);
430372
431 const ino = (try dir.stat()).inode;
432 try testing.expectEqual(sub_ino, ino);
373 const ino = (try dir.stat(io)).inode;
374 try expectEqual(sub_ino, ino);
433375 }
434376
435377 {
436378 // Can open subdir + "..", with some extra "."
437 const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".", "..", "." });
379 const dir_path = try Dir.path.join(testing.allocator, &.{ sub_path, ".", "..", "." });
438380 defer testing.allocator.free(dir_path);
439381
440 var dir = try fs.openDirAbsolute(dir_path, .{});
441 defer dir.close();
382 var dir = try Dir.openDirAbsolute(io, dir_path, .{});
383 defer dir.close(io);
442384
443 const ino = (try dir.stat()).inode;
444 try testing.expectEqual(tmp_ino, ino);
385 const ino = (try dir.stat(io)).inode;
386 try expectEqual(tmp_ino, ino);
445387 }
446388}
447389
448390test "openDir cwd parent '..'" {
449 var dir = fs.cwd().openDir("..", .{}) catch |err| {
391 const io = testing.io;
392
393 var dir = Dir.cwd().openDir(io, "..", .{}) catch |err| {
450394 if (native_os == .wasi and err == error.PermissionDenied) {
451395 return; // This is okay. WASI disallows escaping from the fs sandbox
452396 }
453397 return err;
454398 };
455 defer dir.close();
399 defer dir.close(io);
456400}
457401
458402test "openDir non-cwd parent '..'" {
......@@ -461,69 +405,76 @@ test "openDir non-cwd parent '..'" {
461405 else => {},
462406 }
463407
408 const io = testing.io;
409 const gpa = testing.allocator;
410
464411 var tmp = tmpDir(.{});
465412 defer tmp.cleanup();
466413
467 var subdir = try tmp.dir.makeOpenPath("subdir", .{});
468 defer subdir.close();
414 var subdir = try tmp.dir.createDirPathOpen(io, "subdir", .{});
415 defer subdir.close(io);
469416
470 var dir = try subdir.openDir("..", .{});
471 defer dir.close();
417 var dir = try subdir.openDir(io, "..", .{});
418 defer dir.close(io);
472419
473 const expected_path = try tmp.dir.realpathAlloc(testing.allocator, ".");
474 defer testing.allocator.free(expected_path);
420 const expected_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
421 defer gpa.free(expected_path);
475422
476 const actual_path = try dir.realpathAlloc(testing.allocator, ".");
477 defer testing.allocator.free(actual_path);
423 const actual_path = try dir.realPathFileAlloc(io, ".", gpa);
424 defer gpa.free(actual_path);
478425
479 try testing.expectEqualStrings(expected_path, actual_path);
426 try expectEqualStrings(expected_path, actual_path);
480427}
481428
482429test "readLinkAbsolute" {
483430 if (native_os == .wasi) return error.SkipZigTest;
484431 if (native_os == .openbsd) return error.SkipZigTest;
485432
433 const io = testing.io;
434
486435 var tmp = tmpDir(.{});
487436 defer tmp.cleanup();
488437
489438 // Create some targets
490 try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" });
491 try tmp.dir.makeDir("subdir");
439 try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = "nonsense" });
440 try tmp.dir.createDir(io, "subdir", .default_dir);
492441
493442 // Get base abs path
494 var arena = ArenaAllocator.init(testing.allocator);
495 defer arena.deinit();
496 const allocator = arena.allocator();
443 var arena_allocator = ArenaAllocator.init(testing.allocator);
444 defer arena_allocator.deinit();
445 const arena = arena_allocator.allocator();
497446
498 const base_path = try tmp.dir.realpathAlloc(allocator, ".");
447 const base_path = try tmp.dir.realPathFileAlloc(io, ".", arena);
499448
500449 {
501 const target_path = try fs.path.join(allocator, &.{ base_path, "file.txt" });
502 const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" });
450 const target_path = try Dir.path.join(arena, &.{ base_path, "file.txt" });
451 const symlink_path = try Dir.path.join(arena, &.{ base_path, "symlink1" });
503452
504453 // Create symbolic link by path
505 try setupSymlinkAbsolute(target_path, symlink_path, .{});
506 try testReadLinkAbsolute(target_path, symlink_path);
454 try setupSymlinkAbsolute(io, target_path, symlink_path, .{});
455 try testReadLinkAbsolute(io, target_path, symlink_path);
507456 }
508457 {
509 const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" });
510 const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" });
458 const target_path = try Dir.path.join(arena, &.{ base_path, "subdir" });
459 const symlink_path = try Dir.path.join(arena, &.{ base_path, "symlink2" });
511460
512461 // Create symbolic link to a directory by path
513 try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true });
514 try testReadLinkAbsolute(target_path, symlink_path);
462 try setupSymlinkAbsolute(io, target_path, symlink_path, .{ .is_directory = true });
463 try testReadLinkAbsolute(io, target_path, symlink_path);
515464 }
516465}
517466
518467test "Dir.Iterator" {
468 const io = testing.io;
469
519470 var tmp_dir = tmpDir(.{ .iterate = true });
520471 defer tmp_dir.cleanup();
521472
522473 // First, create a couple of entries to iterate over.
523 const file = try tmp_dir.dir.createFile("some_file", .{});
524 file.close();
474 const file = try tmp_dir.dir.createFile(io, "some_file", .{});
475 file.close(io);
525476
526 try tmp_dir.dir.makeDir("some_dir");
477 try tmp_dir.dir.createDir(io, "some_dir", .default_dir);
527478
528479 var arena = ArenaAllocator.init(testing.allocator);
529480 defer arena.deinit();
......@@ -533,19 +484,21 @@ test "Dir.Iterator" {
533484
534485 // Create iterator.
535486 var iter = tmp_dir.dir.iterate();
536 while (try iter.next()) |entry| {
487 while (try iter.next(io)) |entry| {
537488 // We cannot just store `entry` as on Windows, we're re-using the name buffer
538489 // which means we'll actually share the `name` pointer between entries!
539490 const name = try allocator.dupe(u8, entry.name);
540 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
491 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind, .inode = 0 });
541492 }
542493
543 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
544 try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
545 try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
494 try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
495 try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 }));
496 try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 }));
546497}
547498
548499test "Dir.Iterator many entries" {
500 const io = testing.io;
501
549502 var tmp_dir = tmpDir(.{ .iterate = true });
550503 defer tmp_dir.cleanup();
551504
......@@ -554,8 +507,8 @@ test "Dir.Iterator many entries" {
554507 var buf: [4]u8 = undefined; // Enough to store "1024".
555508 while (i < num) : (i += 1) {
556509 const name = try std.fmt.bufPrint(&buf, "{}", .{i});
557 const file = try tmp_dir.dir.createFile(name, .{});
558 file.close();
510 const file = try tmp_dir.dir.createFile(io, name, .{});
511 file.close(io);
559512 }
560513
561514 var arena = ArenaAllocator.init(testing.allocator);
......@@ -566,29 +519,31 @@ test "Dir.Iterator many entries" {
566519
567520 // Create iterator.
568521 var iter = tmp_dir.dir.iterate();
569 while (try iter.next()) |entry| {
522 while (try iter.next(io)) |entry| {
570523 // We cannot just store `entry` as on Windows, we're re-using the name buffer
571524 // which means we'll actually share the `name` pointer between entries!
572525 const name = try allocator.dupe(u8, entry.name);
573 try entries.append(.{ .name = name, .kind = entry.kind });
526 try entries.append(.{ .name = name, .kind = entry.kind, .inode = 0 });
574527 }
575528
576529 i = 0;
577530 while (i < num) : (i += 1) {
578531 const name = try std.fmt.bufPrint(&buf, "{}", .{i});
579 try testing.expect(contains(&entries, .{ .name = name, .kind = .file }));
532 try expect(contains(&entries, .{ .name = name, .kind = .file, .inode = 0 }));
580533 }
581534}
582535
583536test "Dir.Iterator twice" {
537 const io = testing.io;
538
584539 var tmp_dir = tmpDir(.{ .iterate = true });
585540 defer tmp_dir.cleanup();
586541
587542 // First, create a couple of entries to iterate over.
588 const file = try tmp_dir.dir.createFile("some_file", .{});
589 file.close();
543 const file = try tmp_dir.dir.createFile(io, "some_file", .{});
544 file.close(io);
590545
591 try tmp_dir.dir.makeDir("some_dir");
546 try tmp_dir.dir.createDir(io, "some_dir", .default_dir);
592547
593548 var arena = ArenaAllocator.init(testing.allocator);
594549 defer arena.deinit();
......@@ -600,28 +555,30 @@ test "Dir.Iterator twice" {
600555
601556 // Create iterator.
602557 var iter = tmp_dir.dir.iterate();
603 while (try iter.next()) |entry| {
558 while (try iter.next(io)) |entry| {
604559 // We cannot just store `entry` as on Windows, we're re-using the name buffer
605560 // which means we'll actually share the `name` pointer between entries!
606561 const name = try allocator.dupe(u8, entry.name);
607 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
562 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind, .inode = 0 });
608563 }
609564
610 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
611 try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
612 try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
565 try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
566 try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 }));
567 try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 }));
613568 }
614569}
615570
616571test "Dir.Iterator reset" {
572 const io = testing.io;
573
617574 var tmp_dir = tmpDir(.{ .iterate = true });
618575 defer tmp_dir.cleanup();
619576
620577 // First, create a couple of entries to iterate over.
621 const file = try tmp_dir.dir.createFile("some_file", .{});
622 file.close();
578 const file = try tmp_dir.dir.createFile(io, "some_file", .{});
579 file.close(io);
623580
624 try tmp_dir.dir.makeDir("some_dir");
581 try tmp_dir.dir.createDir(io, "some_dir", .default_dir);
625582
626583 var arena = ArenaAllocator.init(testing.allocator);
627584 defer arena.deinit();
......@@ -634,48 +591,45 @@ test "Dir.Iterator reset" {
634591 while (i < 2) : (i += 1) {
635592 var entries = std.array_list.Managed(Dir.Entry).init(allocator);
636593
637 while (try iter.next()) |entry| {
594 while (try iter.next(io)) |entry| {
638595 // We cannot just store `entry` as on Windows, we're re-using the name buffer
639596 // which means we'll actually share the `name` pointer between entries!
640597 const name = try allocator.dupe(u8, entry.name);
641 try entries.append(.{ .name = name, .kind = entry.kind });
598 try entries.append(.{ .name = name, .kind = entry.kind, .inode = 0 });
642599 }
643600
644 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
645 try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));
646 try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));
601 try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
602 try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 }));
603 try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 }));
647604
648 iter.reset();
605 iter.reader.reset();
649606 }
650607}
651608
652609test "Dir.Iterator but dir is deleted during iteration" {
610 const io = testing.io;
611
653612 var tmp = std.testing.tmpDir(.{});
654613 defer tmp.cleanup();
655614
656615 // Create directory and setup an iterator for it
657 var subdir = try tmp.dir.makeOpenPath("subdir", .{ .iterate = true });
658 defer subdir.close();
616 var subdir = try tmp.dir.createDirPathOpen(io, "subdir", .{ .open_options = .{ .iterate = true } });
617 defer subdir.close(io);
659618
660619 var iterator = subdir.iterate();
661620
662621 // Create something to iterate over within the subdir
663 try tmp.dir.makePath("subdir" ++ fs.path.sep_str ++ "b");
622 try tmp.dir.createDirPath(io, "subdir" ++ Dir.path.sep_str ++ "b");
664623
665624 // Then, before iterating, delete the directory that we're iterating.
666625 // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc.
667626 // If we get an error while trying to delete, we can skip this test (this will happen on platforms
668627 // like Windows which will give FileBusy if the directory is currently open for iteration).
669 tmp.dir.deleteTree("subdir") catch return error.SkipZigTest;
628 tmp.dir.deleteTree(io, "subdir") catch return error.SkipZigTest;
670629
671630 // Now, when we try to iterate, the next call should return null immediately.
672 const entry = try iterator.next();
673 try std.testing.expect(entry == null);
674
675 // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux`
676 if (native_os == .linux) {
677 try std.testing.expectError(error.DirNotFound, iterator.nextLinux());
678 }
631 const entry = try iterator.next(io);
632 try testing.expect(entry == null);
679633}
680634
681635fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool {
......@@ -689,112 +643,122 @@ fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bo
689643 return false;
690644}
691645
692test "Dir.realpath smoke test" {
693 if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest;
646test "Dir.realPath smoke test" {
647 if (native_os == .wasi) return error.SkipZigTest;
694648
695649 try testWithAllSupportedPathTypes(struct {
696650 fn impl(ctx: *TestContext) !void {
697 const allocator = ctx.arena.allocator();
651 const io = ctx.io;
652 const arena = ctx.arena.allocator();
698653 const test_file_path = try ctx.transformPath("test_file");
699654 const test_dir_path = try ctx.transformPath("test_dir");
700 var buf: [fs.max_path_bytes]u8 = undefined;
655 var buf: [Dir.max_path_bytes]u8 = undefined;
701656
702657 // FileNotFound if the path doesn't exist
703 try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_file_path));
704 try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf));
705 try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_dir_path));
706 try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf));
658 try expectError(error.FileNotFound, ctx.dir.realPathFileAlloc(io, test_file_path, arena));
659 try expectError(error.FileNotFound, ctx.dir.realPathFile(io, test_file_path, &buf));
660 try expectError(error.FileNotFound, ctx.dir.realPathFileAlloc(io, test_dir_path, arena));
661 try expectError(error.FileNotFound, ctx.dir.realPathFile(io, test_dir_path, &buf));
707662
708663 // Now create the file and dir
709 try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" });
710 try ctx.dir.makeDir(test_dir_path);
664 try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" });
665 try ctx.dir.createDir(io, test_dir_path, .default_dir);
711666
712667 const base_path = try ctx.transformPath(".");
713 const base_realpath = try ctx.dir.realpathAlloc(allocator, base_path);
714 const expected_file_path = try fs.path.join(
715 allocator,
716 &.{ base_realpath, "test_file" },
717 );
718 const expected_dir_path = try fs.path.join(
719 allocator,
720 &.{ base_realpath, "test_dir" },
721 );
668 const base_realpath = try ctx.dir.realPathFileAlloc(io, base_path, arena);
669 const expected_file_path = try Dir.path.join(arena, &.{ base_realpath, "test_file" });
670 const expected_dir_path = try Dir.path.join(arena, &.{ base_realpath, "test_dir" });
722671
723672 // First, test non-alloc version
724673 {
725 const file_path = try ctx.dir.realpath(test_file_path, &buf);
726 try testing.expectEqualStrings(expected_file_path, file_path);
674 const file_path = buf[0..try ctx.dir.realPathFile(io, test_file_path, &buf)];
675 try expectEqualStrings(expected_file_path, file_path);
727676
728 const dir_path = try ctx.dir.realpath(test_dir_path, &buf);
729 try testing.expectEqualStrings(expected_dir_path, dir_path);
677 const dir_path = buf[0..try ctx.dir.realPathFile(io, test_dir_path, &buf)];
678 try expectEqualStrings(expected_dir_path, dir_path);
730679 }
731680
732681 // Next, test alloc version
733682 {
734 const file_path = try ctx.dir.realpathAlloc(allocator, test_file_path);
735 try testing.expectEqualStrings(expected_file_path, file_path);
683 const file_path = try ctx.dir.realPathFileAlloc(io, test_file_path, arena);
684 try expectEqualStrings(expected_file_path, file_path);
736685
737 const dir_path = try ctx.dir.realpathAlloc(allocator, test_dir_path);
738 try testing.expectEqualStrings(expected_dir_path, dir_path);
686 const dir_path = try ctx.dir.realPathFileAlloc(io, test_dir_path, arena);
687 try expectEqualStrings(expected_dir_path, dir_path);
739688 }
740689 }
741690 }.impl);
742691}
743692
744693test "readFileAlloc" {
694 const io = testing.io;
695
745696 var tmp_dir = tmpDir(.{});
746697 defer tmp_dir.cleanup();
747698
748 var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });
749 defer file.close();
699 var file = try tmp_dir.dir.createFile(io, "test_file", .{ .read = true });
700 defer file.close(io);
750701
751 const buf1 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(1024));
702 const buf1 = try tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(1024));
752703 defer testing.allocator.free(buf1);
753 try testing.expectEqualStrings("", buf1);
704 try expectEqualStrings("", buf1);
754705
755706 const write_buf: []const u8 = "this is a test.\nthis is a test.\nthis is a test.\nthis is a test.\n";
756 try file.writeAll(write_buf);
707 try file.writeStreamingAll(io, write_buf);
757708
758709 {
759710 // max_bytes > file_size
760 const buf2 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(1024));
711 const buf2 = try tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(1024));
761712 defer testing.allocator.free(buf2);
762 try testing.expectEqualStrings(write_buf, buf2);
713 try expectEqualStrings(write_buf, buf2);
763714 }
764715
765716 {
766717 // max_bytes == file_size
767 try testing.expectError(
718 try expectError(
768719 error.StreamTooLong,
769 tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(write_buf.len)),
720 tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(write_buf.len)),
770721 );
771722 }
772723
773724 {
774725 // max_bytes == file_size + 1
775 const buf2 = try tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(write_buf.len + 1));
726 const buf2 = try tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(write_buf.len + 1));
776727 defer testing.allocator.free(buf2);
777 try testing.expectEqualStrings(write_buf, buf2);
728 try expectEqualStrings(write_buf, buf2);
778729 }
779730
780731 // max_bytes < file_size
781 try testing.expectError(
732 try expectError(
782733 error.StreamTooLong,
783 tmp_dir.dir.readFileAlloc("test_file", testing.allocator, .limited(write_buf.len - 1)),
734 tmp_dir.dir.readFileAlloc(io, "test_file", testing.allocator, .limited(write_buf.len - 1)),
784735 );
785736}
786737
787738test "Dir.statFile" {
788739 try testWithAllSupportedPathTypes(struct {
789740 fn impl(ctx: *TestContext) !void {
790 const test_file_name = try ctx.transformPath("test_file");
741 const io = ctx.io;
742 {
743 const test_file_name = try ctx.transformPath("test_file");
791744
792 try testing.expectError(error.FileNotFound, ctx.dir.statFile(test_file_name));
745 try expectError(error.FileNotFound, ctx.dir.statFile(io, test_file_name, .{}));
793746
794 try ctx.dir.writeFile(.{ .sub_path = test_file_name, .data = "" });
747 try ctx.dir.writeFile(io, .{ .sub_path = test_file_name, .data = "" });
795748
796 const stat = try ctx.dir.statFile(test_file_name);
797 try testing.expectEqual(File.Kind.file, stat.kind);
749 const stat = try ctx.dir.statFile(io, test_file_name, .{});
750 try expectEqual(.file, stat.kind);
751 }
752 {
753 const test_dir_name = try ctx.transformPath("test_dir");
754
755 try expectError(error.FileNotFound, ctx.dir.statFile(io, test_dir_name, .{}));
756
757 try ctx.dir.createDir(io, test_dir_name, .default_dir);
758
759 const stat = try ctx.dir.statFile(io, test_dir_name, .{});
760 try expectEqual(.directory, stat.kind);
761 }
798762 }
799763 }.impl);
800764}
......@@ -802,12 +766,13 @@ test "Dir.statFile" {
802766test "statFile on dangling symlink" {
803767 try testWithAllSupportedPathTypes(struct {
804768 fn impl(ctx: *TestContext) !void {
769 const io = ctx.io;
805770 const symlink_name = try ctx.transformPath("dangling-symlink");
806 const symlink_target = "." ++ fs.path.sep_str ++ "doesnotexist";
771 const symlink_target = "." ++ Dir.path.sep_str ++ "doesnotexist";
807772
808 try setupSymlink(ctx.dir, symlink_target, symlink_name, .{});
773 try setupSymlink(io, ctx.dir, symlink_target, symlink_name, .{});
809774
810 try std.testing.expectError(error.FileNotFound, ctx.dir.statFile(symlink_name));
775 try expectError(error.FileNotFound, ctx.dir.statFile(io, symlink_name, .{}));
811776 }
812777 }.impl);
813778}
......@@ -815,25 +780,27 @@ test "statFile on dangling symlink" {
815780test "directory operations on files" {
816781 try testWithAllSupportedPathTypes(struct {
817782 fn impl(ctx: *TestContext) !void {
783 const io = ctx.io;
784
818785 const test_file_name = try ctx.transformPath("test_file");
819786
820 var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
821 file.close();
787 var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true });
788 file.close(io);
822789
823 try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name));
824 try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{}));
825 try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name));
790 try expectError(error.PathAlreadyExists, ctx.dir.createDir(io, test_file_name, .default_dir));
791 try expectError(error.NotDir, ctx.dir.openDir(io, test_file_name, .{}));
792 try expectError(error.NotDir, ctx.dir.deleteDir(io, test_file_name));
826793
827794 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
828 try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name));
829 try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name));
795 try expectError(error.PathAlreadyExists, Dir.createDirAbsolute(io, test_file_name, .default_dir));
796 try expectError(error.NotDir, Dir.deleteDirAbsolute(io, test_file_name));
830797 }
831798
832799 // ensure the file still exists and is a file as a sanity check
833 file = try ctx.dir.openFile(test_file_name, .{});
834 const stat = try file.stat();
835 try testing.expectEqual(File.Kind.file, stat.kind);
836 file.close();
800 file = try ctx.dir.openFile(io, test_file_name, .{});
801 const stat = try file.stat(io);
802 try expectEqual(File.Kind.file, stat.kind);
803 file.close(io);
837804 }
838805 }.impl);
839806}
......@@ -842,81 +809,91 @@ test "file operations on directories" {
842809 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
843810 if (native_os == .freebsd) return error.SkipZigTest;
844811
812 const io = testing.io;
813
845814 try testWithAllSupportedPathTypes(struct {
846815 fn impl(ctx: *TestContext) !void {
847816 const test_dir_name = try ctx.transformPath("test_dir");
848817
849 try ctx.dir.makeDir(test_dir_name);
818 try ctx.dir.createDir(io, test_dir_name, .default_dir);
850819
851 try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{}));
852 try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name));
820 try expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{}));
821 try expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name));
853822 switch (native_os) {
854823 .dragonfly, .netbsd => {
855824 // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732
856 const buf = try ctx.dir.readFileAlloc(test_dir_name, testing.allocator, .unlimited);
825 const buf = try ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited);
857826 testing.allocator.free(buf);
858827 },
859828 .wasi => {
860829 // WASI return EBADF, which gets mapped to NotOpenForReading.
861830 // See https://github.com/bytecodealliance/wasmtime/issues/1935
862 try testing.expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(test_dir_name, testing.allocator, .unlimited));
831 try expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited));
863832 },
864833 else => {
865 try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(test_dir_name, testing.allocator, .unlimited));
834 try expectError(error.IsDir, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited));
866835 },
867836 }
868837
869838 if (native_os == .wasi and builtin.link_libc) {
870839 // wasmtime unexpectedly succeeds here, see https://github.com/ziglang/zig/issues/20747
871 const handle = try ctx.dir.openFile(test_dir_name, .{ .mode = .read_write });
872 handle.close();
840 const handle = try ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write });
841 handle.close(io);
873842 } else {
874843 // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms.
875 // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732
876 try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }));
844 try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write }));
877845 }
878846
847 {
848 const handle = try ctx.dir.openFile(io, test_dir_name, .{ .allow_directory = true, .mode = .read_only });
849 handle.close(io);
850 }
851 try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .allow_directory = false, .mode = .read_only }));
852
879853 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
880 try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{}));
881 try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name));
854 try expectError(error.IsDir, Dir.createFileAbsolute(io, test_dir_name, .{}));
855 try expectError(error.IsDir, Dir.deleteFileAbsolute(io, test_dir_name));
882856 }
883857
884858 // ensure the directory still exists as a sanity check
885 var dir = try ctx.dir.openDir(test_dir_name, .{});
886 dir.close();
859 var dir = try ctx.dir.openDir(io, test_dir_name, .{});
860 dir.close(io);
887861 }
888862 }.impl);
889863}
890864
891test "makeOpenPath parent dirs do not exist" {
865test "createDirPathOpen parent dirs do not exist" {
866 const io = testing.io;
867
892868 var tmp_dir = tmpDir(.{});
893869 defer tmp_dir.cleanup();
894870
895 var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{});
896 dir.close();
871 var dir = try tmp_dir.dir.createDirPathOpen(io, "root_dir/parent_dir/some_dir", .{});
872 dir.close(io);
897873
898874 // double check that the full directory structure was created
899 var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{});
900 dir_verification.close();
875 var dir_verification = try tmp_dir.dir.openDir(io, "root_dir/parent_dir/some_dir", .{});
876 dir_verification.close(io);
901877}
902878
903879test "deleteDir" {
904880 try testWithAllSupportedPathTypes(struct {
905881 fn impl(ctx: *TestContext) !void {
882 const io = ctx.io;
906883 const test_dir_path = try ctx.transformPath("test_dir");
907 const test_file_path = try ctx.transformPath("test_dir" ++ fs.path.sep_str ++ "test_file");
884 const test_file_path = try ctx.transformPath("test_dir" ++ Dir.path.sep_str ++ "test_file");
908885
909886 // deleting a non-existent directory
910 try testing.expectError(error.FileNotFound, ctx.dir.deleteDir(test_dir_path));
887 try expectError(error.FileNotFound, ctx.dir.deleteDir(io, test_dir_path));
911888
912889 // deleting a non-empty directory
913 try ctx.dir.makeDir(test_dir_path);
914 try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" });
915 try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path));
890 try ctx.dir.createDir(io, test_dir_path, .default_dir);
891 try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" });
892 try expectError(error.DirNotEmpty, ctx.dir.deleteDir(io, test_dir_path));
916893
917894 // deleting an empty directory
918 try ctx.dir.deleteFile(test_file_path);
919 try ctx.dir.deleteDir(test_dir_path);
895 try ctx.dir.deleteFile(io, test_file_path);
896 try ctx.dir.deleteDir(io, test_dir_path);
920897 }
921898 }.impl);
922899}
......@@ -924,6 +901,7 @@ test "deleteDir" {
924901test "Dir.rename files" {
925902 try testWithAllSupportedPathTypes(struct {
926903 fn impl(ctx: *TestContext) !void {
904 const io = ctx.io;
927905 // Rename on Windows can hit intermittent AccessDenied errors
928906 // when certain conditions are true about the host system.
929907 // For now, skip this test when the path type is UNC to avoid them.
......@@ -933,32 +911,32 @@ test "Dir.rename files" {
933911 const missing_file_path = try ctx.transformPath("missing_file_name");
934912 const something_else_path = try ctx.transformPath("something_else");
935913
936 try testing.expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, something_else_path));
914 try expectError(error.FileNotFound, ctx.dir.rename(missing_file_path, ctx.dir, something_else_path, io));
937915
938916 // Renaming files
939917 const test_file_name = try ctx.transformPath("test_file");
940918 const renamed_test_file_name = try ctx.transformPath("test_file_renamed");
941 var file = try ctx.dir.createFile(test_file_name, .{ .read = true });
942 file.close();
943 try ctx.dir.rename(test_file_name, renamed_test_file_name);
919 var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true });
920 file.close(io);
921 try ctx.dir.rename(test_file_name, ctx.dir, renamed_test_file_name, io);
944922
945923 // Ensure the file was renamed
946 try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{}));
947 file = try ctx.dir.openFile(renamed_test_file_name, .{});
948 file.close();
924 try expectError(error.FileNotFound, ctx.dir.openFile(io, test_file_name, .{}));
925 file = try ctx.dir.openFile(io, renamed_test_file_name, .{});
926 file.close(io);
949927
950928 // Rename to self succeeds
951 try ctx.dir.rename(renamed_test_file_name, renamed_test_file_name);
929 try ctx.dir.rename(renamed_test_file_name, ctx.dir, renamed_test_file_name, io);
952930
953931 // Rename to existing file succeeds
954932 const existing_file_path = try ctx.transformPath("existing_file");
955 var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true });
956 existing_file.close();
957 try ctx.dir.rename(renamed_test_file_name, existing_file_path);
933 var existing_file = try ctx.dir.createFile(io, existing_file_path, .{ .read = true });
934 existing_file.close(io);
935 try ctx.dir.rename(renamed_test_file_name, ctx.dir, existing_file_path, io);
958936
959 try testing.expectError(error.FileNotFound, ctx.dir.openFile(renamed_test_file_name, .{}));
960 file = try ctx.dir.openFile(existing_file_path, .{});
961 file.close();
937 try expectError(error.FileNotFound, ctx.dir.openFile(io, renamed_test_file_name, .{}));
938 file = try ctx.dir.openFile(io, existing_file_path, .{});
939 file.close(io);
962940 }
963941 }.impl);
964942}
......@@ -966,6 +944,8 @@ test "Dir.rename files" {
966944test "Dir.rename directories" {
967945 try testWithAllSupportedPathTypes(struct {
968946 fn impl(ctx: *TestContext) !void {
947 const io = ctx.io;
948
969949 // Rename on Windows can hit intermittent AccessDenied errors
970950 // when certain conditions are true about the host system.
971951 // For now, skip this test when the path type is UNC to avoid them.
......@@ -976,27 +956,27 @@ test "Dir.rename directories" {
976956 const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed");
977957
978958 // Renaming directories
979 try ctx.dir.makeDir(test_dir_path);
980 try ctx.dir.rename(test_dir_path, test_dir_renamed_path);
959 try ctx.dir.createDir(io, test_dir_path, .default_dir);
960 try ctx.dir.rename(test_dir_path, ctx.dir, test_dir_renamed_path, io);
981961
982962 // Ensure the directory was renamed
983 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
984 var dir = try ctx.dir.openDir(test_dir_renamed_path, .{});
963 try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{}));
964 var dir = try ctx.dir.openDir(io, test_dir_renamed_path, .{});
985965
986966 // Put a file in the directory
987 var file = try dir.createFile("test_file", .{ .read = true });
988 file.close();
989 dir.close();
967 var file = try dir.createFile(io, "test_file", .{ .read = true });
968 file.close(io);
969 dir.close(io);
990970
991971 const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again");
992 try ctx.dir.rename(test_dir_renamed_path, test_dir_renamed_again_path);
972 try ctx.dir.rename(test_dir_renamed_path, ctx.dir, test_dir_renamed_again_path, io);
993973
994974 // Ensure the directory was renamed and the file still exists in it
995 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_renamed_path, .{}));
996 dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{});
997 file = try dir.openFile("test_file", .{});
998 file.close();
999 dir.close();
975 try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_renamed_path, .{}));
976 dir = try ctx.dir.openDir(io, test_dir_renamed_again_path, .{});
977 file = try dir.openFile(io, "test_file", .{});
978 file.close(io);
979 dir.close(io);
1000980 }
1001981 }.impl);
1002982}
......@@ -1007,17 +987,19 @@ test "Dir.rename directory onto empty dir" {
1007987
1008988 try testWithAllSupportedPathTypes(struct {
1009989 fn impl(ctx: *TestContext) !void {
990 const io = ctx.io;
991
1010992 const test_dir_path = try ctx.transformPath("test_dir");
1011993 const target_dir_path = try ctx.transformPath("target_dir_path");
1012994
1013 try ctx.dir.makeDir(test_dir_path);
1014 try ctx.dir.makeDir(target_dir_path);
1015 try ctx.dir.rename(test_dir_path, target_dir_path);
995 try ctx.dir.createDir(io, test_dir_path, .default_dir);
996 try ctx.dir.createDir(io, target_dir_path, .default_dir);
997 try ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io);
1016998
1017999 // Ensure the directory was renamed
1018 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));
1019 var dir = try ctx.dir.openDir(target_dir_path, .{});
1020 dir.close();
1000 try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{}));
1001 var dir = try ctx.dir.openDir(io, target_dir_path, .{});
1002 dir.close(io);
10211003 }
10221004 }.impl);
10231005}
......@@ -1028,22 +1010,23 @@ test "Dir.rename directory onto non-empty dir" {
10281010
10291011 try testWithAllSupportedPathTypes(struct {
10301012 fn impl(ctx: *TestContext) !void {
1013 const io = ctx.io;
10311014 const test_dir_path = try ctx.transformPath("test_dir");
10321015 const target_dir_path = try ctx.transformPath("target_dir_path");
10331016
1034 try ctx.dir.makeDir(test_dir_path);
1017 try ctx.dir.createDir(io, test_dir_path, .default_dir);
10351018
1036 var target_dir = try ctx.dir.makeOpenPath(target_dir_path, .{});
1037 var file = try target_dir.createFile("test_file", .{ .read = true });
1038 file.close();
1039 target_dir.close();
1019 var target_dir = try ctx.dir.createDirPathOpen(io, target_dir_path, .{});
1020 var file = try target_dir.createFile(io, "test_file", .{ .read = true });
1021 file.close(io);
1022 target_dir.close(io);
10401023
10411024 // Rename should fail with PathAlreadyExists if target_dir is non-empty
1042 try testing.expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, target_dir_path));
1025 try expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io));
10431026
10441027 // Ensure the directory was not renamed
1045 var dir = try ctx.dir.openDir(test_dir_path, .{});
1046 dir.close();
1028 var dir = try ctx.dir.openDir(io, test_dir_path, .{});
1029 dir.close(io);
10471030 }
10481031 }.impl);
10491032}
......@@ -1054,19 +1037,22 @@ test "Dir.rename file <-> dir" {
10541037
10551038 try testWithAllSupportedPathTypes(struct {
10561039 fn impl(ctx: *TestContext) !void {
1040 const io = ctx.io;
10571041 const test_file_path = try ctx.transformPath("test_file");
10581042 const test_dir_path = try ctx.transformPath("test_dir");
10591043
1060 var file = try ctx.dir.createFile(test_file_path, .{ .read = true });
1061 file.close();
1062 try ctx.dir.makeDir(test_dir_path);
1063 try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path));
1064 try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path));
1044 var file = try ctx.dir.createFile(io, test_file_path, .{ .read = true });
1045 file.close(io);
1046 try ctx.dir.createDir(io, test_dir_path, .default_dir);
1047 try expectError(error.IsDir, ctx.dir.rename(test_file_path, ctx.dir, test_dir_path, io));
1048 try expectError(error.NotDir, ctx.dir.rename(test_dir_path, ctx.dir, test_file_path, io));
10651049 }
10661050 }.impl);
10671051}
10681052
10691053test "rename" {
1054 const io = testing.io;
1055
10701056 var tmp_dir1 = tmpDir(.{});
10711057 defer tmp_dir1.cleanup();
10721058
......@@ -1076,20 +1062,22 @@ test "rename" {
10761062 // Renaming files
10771063 const test_file_name = "test_file";
10781064 const renamed_test_file_name = "test_file_renamed";
1079 var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true });
1080 file.close();
1081 try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name);
1065 var file = try tmp_dir1.dir.createFile(io, test_file_name, .{ .read = true });
1066 file.close(io);
1067 try Dir.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name, io);
10821068
10831069 // ensure the file was renamed
1084 try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{}));
1085 file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{});
1086 file.close();
1070 try expectError(error.FileNotFound, tmp_dir1.dir.openFile(io, test_file_name, .{}));
1071 file = try tmp_dir2.dir.openFile(io, renamed_test_file_name, .{});
1072 file.close(io);
10871073}
10881074
10891075test "renameAbsolute" {
10901076 if (native_os == .wasi) return error.SkipZigTest;
10911077 if (native_os == .openbsd) return error.SkipZigTest;
10921078
1079 const io = testing.io;
1080
10931081 var tmp_dir = tmpDir(.{});
10941082 defer tmp_dir.cleanup();
10951083
......@@ -1098,289 +1086,359 @@ test "renameAbsolute" {
10981086 defer arena.deinit();
10991087 const allocator = arena.allocator();
11001088
1101 const base_path = try tmp_dir.dir.realpathAlloc(allocator, ".");
1089 const base_path = try tmp_dir.dir.realPathFileAlloc(io, ".", allocator);
11021090
1103 try testing.expectError(error.FileNotFound, fs.renameAbsolute(
1104 try fs.path.join(allocator, &.{ base_path, "missing_file_name" }),
1105 try fs.path.join(allocator, &.{ base_path, "something_else" }),
1091 try expectError(error.FileNotFound, Dir.renameAbsolute(
1092 try Dir.path.join(allocator, &.{ base_path, "missing_file_name" }),
1093 try Dir.path.join(allocator, &.{ base_path, "something_else" }),
1094 io,
11061095 ));
11071096
11081097 // Renaming files
11091098 const test_file_name = "test_file";
11101099 const renamed_test_file_name = "test_file_renamed";
1111 var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
1112 file.close();
1113 try fs.renameAbsolute(
1114 try fs.path.join(allocator, &.{ base_path, test_file_name }),
1115 try fs.path.join(allocator, &.{ base_path, renamed_test_file_name }),
1100 var file = try tmp_dir.dir.createFile(io, test_file_name, .{ .read = true });
1101 file.close(io);
1102 try Dir.renameAbsolute(
1103 try Dir.path.join(allocator, &.{ base_path, test_file_name }),
1104 try Dir.path.join(allocator, &.{ base_path, renamed_test_file_name }),
1105 io,
11161106 );
11171107
11181108 // ensure the file was renamed
1119 try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
1120 file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
1121 const stat = try file.stat();
1122 try testing.expectEqual(File.Kind.file, stat.kind);
1123 file.close();
1109 try expectError(error.FileNotFound, tmp_dir.dir.openFile(io, test_file_name, .{}));
1110 file = try tmp_dir.dir.openFile(io, renamed_test_file_name, .{});
1111 const stat = try file.stat(io);
1112 try expectEqual(File.Kind.file, stat.kind);
1113 file.close(io);
11241114
11251115 // Renaming directories
11261116 const test_dir_name = "test_dir";
11271117 const renamed_test_dir_name = "test_dir_renamed";
1128 try tmp_dir.dir.makeDir(test_dir_name);
1129 try fs.renameAbsolute(
1130 try fs.path.join(allocator, &.{ base_path, test_dir_name }),
1131 try fs.path.join(allocator, &.{ base_path, renamed_test_dir_name }),
1118 try tmp_dir.dir.createDir(io, test_dir_name, .default_dir);
1119 try Dir.renameAbsolute(
1120 try Dir.path.join(allocator, &.{ base_path, test_dir_name }),
1121 try Dir.path.join(allocator, &.{ base_path, renamed_test_dir_name }),
1122 io,
11321123 );
11331124
11341125 // ensure the directory was renamed
1135 try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{}));
1136 var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{});
1137 dir.close();
1126 try expectError(error.FileNotFound, tmp_dir.dir.openDir(io, test_dir_name, .{}));
1127 var dir = try tmp_dir.dir.openDir(io, renamed_test_dir_name, .{});
1128 dir.close(io);
11381129}
11391130
1140test "openSelfExe" {
1131test "openExecutable" {
11411132 if (native_os == .wasi) return error.SkipZigTest;
11421133
1143 const self_exe_file = try std.fs.openSelfExe(.{});
1144 self_exe_file.close();
1134 const io = testing.io;
1135
1136 const self_exe_file = try std.process.openExecutable(io, .{});
1137 self_exe_file.close(io);
11451138}
11461139
1147test "selfExePath" {
1140test "executablePath" {
11481141 if (native_os == .wasi) return error.SkipZigTest;
11491142
1150 var buf: [fs.max_path_bytes]u8 = undefined;
1151 const buf_self_exe_path = try std.fs.selfExePath(&buf);
1152 const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator);
1143 const io = testing.io;
1144 var buf: [Dir.max_path_bytes]u8 = undefined;
1145 const len = try std.process.executablePath(io, &buf);
1146 const buf_self_exe_path = buf[0..len];
1147 const alloc_self_exe_path = try std.process.executablePathAlloc(io, testing.allocator);
11531148 defer testing.allocator.free(alloc_self_exe_path);
1154 try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path);
1149 try expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path);
11551150}
11561151
11571152test "deleteTree does not follow symlinks" {
1153 const io = testing.io;
1154
11581155 var tmp = tmpDir(.{});
11591156 defer tmp.cleanup();
11601157
1161 try tmp.dir.makePath("b");
1158 try tmp.dir.createDirPath(io, "b");
11621159 {
1163 var a = try tmp.dir.makeOpenPath("a", .{});
1164 defer a.close();
1160 var a = try tmp.dir.createDirPathOpen(io, "a", .{});
1161 defer a.close(io);
11651162
1166 try setupSymlink(a, "../b", "b", .{ .is_directory = true });
1163 try setupSymlink(io, a, "../b", "b", .{ .is_directory = true });
11671164 }
11681165
1169 try tmp.dir.deleteTree("a");
1166 try tmp.dir.deleteTree(io, "a");
11701167
1171 try testing.expectError(error.FileNotFound, tmp.dir.access("a", .{}));
1172 try tmp.dir.access("b", .{});
1168 try expectError(error.FileNotFound, tmp.dir.access(io, "a", .{}));
1169 try tmp.dir.access(io, "b", .{});
11731170}
11741171
11751172test "deleteTree on a symlink" {
1173 const io = testing.io;
1174
11761175 var tmp = tmpDir(.{});
11771176 defer tmp.cleanup();
11781177
11791178 // Symlink to a file
1180 try tmp.dir.writeFile(.{ .sub_path = "file", .data = "" });
1181 try setupSymlink(tmp.dir, "file", "filelink", .{});
1179 try tmp.dir.writeFile(io, .{ .sub_path = "file", .data = "" });
1180 try setupSymlink(io, tmp.dir, "file", "filelink", .{});
11821181
1183 try tmp.dir.deleteTree("filelink");
1184 try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{}));
1185 try tmp.dir.access("file", .{});
1182 try tmp.dir.deleteTree(io, "filelink");
1183 try expectError(error.FileNotFound, tmp.dir.access(io, "filelink", .{}));
1184 try tmp.dir.access(io, "file", .{});
11861185
11871186 // Symlink to a directory
1188 try tmp.dir.makePath("dir");
1189 try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true });
1187 try tmp.dir.createDirPath(io, "dir");
1188 try setupSymlink(io, tmp.dir, "dir", "dirlink", .{ .is_directory = true });
11901189
1191 try tmp.dir.deleteTree("dirlink");
1192 try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{}));
1193 try tmp.dir.access("dir", .{});
1190 try tmp.dir.deleteTree(io, "dirlink");
1191 try expectError(error.FileNotFound, tmp.dir.access(io, "dirlink", .{}));
1192 try tmp.dir.access(io, "dir", .{});
11941193}
11951194
1196test "makePath, put some files in it, deleteTree" {
1195test "createDirPath, put some files in it, deleteTree" {
11971196 try testWithAllSupportedPathTypes(struct {
11981197 fn impl(ctx: *TestContext) !void {
1198 const io = ctx.io;
11991199 const allocator = ctx.arena.allocator();
12001200 const dir_path = try ctx.transformPath("os_test_tmp");
12011201
1202 try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
1203 try ctx.dir.writeFile(.{
1204 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),
1202 try ctx.dir.createDirPath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
1203 try ctx.dir.writeFile(io, .{
1204 .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),
12051205 .data = "nonsense",
12061206 });
1207 try ctx.dir.writeFile(.{
1208 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),
1207 try ctx.dir.writeFile(io, .{
1208 .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),
12091209 .data = "blah",
12101210 });
12111211
1212 try ctx.dir.deleteTree(dir_path);
1213 try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
1212 try ctx.dir.deleteTree(io, dir_path);
1213 try expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{}));
12141214 }
12151215 }.impl);
12161216}
12171217
1218test "makePath, put some files in it, deleteTreeMinStackSize" {
1218test "createDirPath, put some files in it, deleteTreeMinStackSize" {
12191219 try testWithAllSupportedPathTypes(struct {
12201220 fn impl(ctx: *TestContext) !void {
1221 const io = ctx.io;
12211222 const allocator = ctx.arena.allocator();
12221223 const dir_path = try ctx.transformPath("os_test_tmp");
12231224
1224 try ctx.dir.makePath(try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
1225 try ctx.dir.writeFile(.{
1226 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),
1225 try ctx.dir.createDirPath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
1226 try ctx.dir.writeFile(io, .{
1227 .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),
12271228 .data = "nonsense",
12281229 });
1229 try ctx.dir.writeFile(.{
1230 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),
1230 try ctx.dir.writeFile(io, .{
1231 .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),
12311232 .data = "blah",
12321233 });
12331234
1234 try ctx.dir.deleteTreeMinStackSize(dir_path);
1235 try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));
1235 try ctx.dir.deleteTreeMinStackSize(io, dir_path);
1236 try expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{}));
12361237 }
12371238 }.impl);
12381239}
12391240
1240test "makePath in a directory that no longer exists" {
1241test "createDirPath in a directory that no longer exists" {
12411242 if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir
12421243
1244 const io = testing.io;
1245
12431246 var tmp = tmpDir(.{});
12441247 defer tmp.cleanup();
1245 try tmp.parent_dir.deleteTree(&tmp.sub_path);
1248 try tmp.parent_dir.deleteTree(io, &tmp.sub_path);
12461249
1247 try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path"));
1250 try expectError(error.FileNotFound, tmp.dir.createDirPath(io, "sub-path"));
12481251}
12491252
1250test "makePath but sub_path contains pre-existing file" {
1253test "createDirPath but sub_path contains pre-existing file" {
1254 const io = testing.io;
1255
12511256 var tmp = tmpDir(.{});
12521257 defer tmp.cleanup();
12531258
1254 try tmp.dir.makeDir("foo");
1255 try tmp.dir.writeFile(.{ .sub_path = "foo/bar", .data = "" });
1259 try tmp.dir.createDir(io, "foo", .default_dir);
1260 try tmp.dir.writeFile(io, .{ .sub_path = "foo/bar", .data = "" });
12561261
1257 try testing.expectError(error.NotDir, tmp.dir.makePath("foo/bar/baz"));
1262 try expectError(error.NotDir, tmp.dir.createDirPath(io, "foo/bar/baz"));
12581263}
12591264
1260fn expectDir(dir: Dir, path: []const u8) !void {
1261 var d = try dir.openDir(path, .{});
1262 d.close();
1265fn expectDir(io: Io, dir: Dir, path: []const u8) !void {
1266 var d = try dir.openDir(io, path, .{});
1267 d.close(io);
12631268}
12641269
12651270test "makepath existing directories" {
1271 const io = testing.io;
1272
12661273 var tmp = tmpDir(.{});
12671274 defer tmp.cleanup();
12681275
1269 try tmp.dir.makeDir("A");
1270 var tmpA = try tmp.dir.openDir("A", .{});
1271 defer tmpA.close();
1272 try tmpA.makeDir("B");
1276 try tmp.dir.createDir(io, "A", .default_dir);
1277 var tmpA = try tmp.dir.openDir(io, "A", .{});
1278 defer tmpA.close(io);
1279 try tmpA.createDir(io, "B", .default_dir);
12731280
1274 const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C";
1275 try tmp.dir.makePath(testPath);
1281 const testPath = "A" ++ Dir.path.sep_str ++ "B" ++ Dir.path.sep_str ++ "C";
1282 try tmp.dir.createDirPath(io, testPath);
12761283
1277 try expectDir(tmp.dir, testPath);
1284 try expectDir(io, tmp.dir, testPath);
12781285}
12791286
12801287test "makepath through existing valid symlink" {
1288 const io = testing.io;
1289
12811290 var tmp = tmpDir(.{});
12821291 defer tmp.cleanup();
12831292
1284 try tmp.dir.makeDir("realfolder");
1285 try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{});
1293 try tmp.dir.createDir(io, "realfolder", .default_dir);
1294 try setupSymlink(io, tmp.dir, "." ++ Dir.path.sep_str ++ "realfolder", "working-symlink", .{});
12861295
1287 try tmp.dir.makePath("working-symlink" ++ fs.path.sep_str ++ "in-realfolder");
1296 try tmp.dir.createDirPath(io, "working-symlink" ++ Dir.path.sep_str ++ "in-realfolder");
12881297
1289 try expectDir(tmp.dir, "realfolder" ++ fs.path.sep_str ++ "in-realfolder");
1298 try expectDir(io, tmp.dir, "realfolder" ++ Dir.path.sep_str ++ "in-realfolder");
12901299}
12911300
12921301test "makepath relative walks" {
1302 const io = testing.io;
1303
12931304 var tmp = tmpDir(.{});
12941305 defer tmp.cleanup();
12951306
1296 const relPath = try fs.path.join(testing.allocator, &.{
1307 const relPath = try Dir.path.join(testing.allocator, &.{
12971308 "first", "..", "second", "..", "third", "..", "first", "A", "..", "B", "..", "C",
12981309 });
12991310 defer testing.allocator.free(relPath);
13001311
1301 try tmp.dir.makePath(relPath);
1312 try tmp.dir.createDirPath(io, relPath);
13021313
13031314 // How .. is handled is different on Windows than non-Windows
13041315 switch (native_os) {
13051316 .windows => {
13061317 // On Windows, .. is resolved before passing the path to NtCreateFile,
13071318 // meaning everything except `first/C` drops out.
1308 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C");
1309 try testing.expectError(error.FileNotFound, tmp.dir.access("second", .{}));
1310 try testing.expectError(error.FileNotFound, tmp.dir.access("third", .{}));
1319 try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "C");
1320 try expectError(error.FileNotFound, tmp.dir.access(io, "second", .{}));
1321 try expectError(error.FileNotFound, tmp.dir.access(io, "third", .{}));
13111322 },
13121323 else => {
1313 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "A");
1314 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "B");
1315 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C");
1316 try expectDir(tmp.dir, "second");
1317 try expectDir(tmp.dir, "third");
1324 try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "A");
1325 try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "B");
1326 try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "C");
1327 try expectDir(io, tmp.dir, "second");
1328 try expectDir(io, tmp.dir, "third");
13181329 },
13191330 }
13201331}
13211332
13221333test "makepath ignores '.'" {
1334 const io = testing.io;
1335
13231336 var tmp = tmpDir(.{});
13241337 defer tmp.cleanup();
13251338
13261339 // Path to create, with "." elements:
1327 const dotPath = try fs.path.join(testing.allocator, &.{
1340 const dotPath = try Dir.path.join(testing.allocator, &.{
13281341 "first", ".", "second", ".", "third",
13291342 });
13301343 defer testing.allocator.free(dotPath);
13311344
13321345 // Path to expect to find:
1333 const expectedPath = try fs.path.join(testing.allocator, &.{
1346 const expectedPath = try Dir.path.join(testing.allocator, &.{
13341347 "first", "second", "third",
13351348 });
13361349 defer testing.allocator.free(expectedPath);
13371350
1338 try tmp.dir.makePath(dotPath);
1351 try tmp.dir.createDirPath(io, dotPath);
13391352
1340 try expectDir(tmp.dir, expectedPath);
1353 try expectDir(io, tmp.dir, expectedPath);
13411354}
13421355
1343fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void {
1344 // setup, create a dir and a nested file both with maxed filenames, and walk the dir
1356fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8, maxed_dirname: []const u8) !void {
1357 // create a file, a dir, and a nested file all with maxed filenames
13451358 {
1346 var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{});
1347 defer maxed_dir.close();
1359 try iterable_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" });
13481360
1349 try maxed_dir.writeFile(.{ .sub_path = maxed_filename, .data = "" });
1361 var maxed_dir = try iterable_dir.createDirPathOpen(io, maxed_dirname, .{});
1362 defer maxed_dir.close(io);
13501363
1364 try maxed_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" });
1365 }
1366 // Low level API with minimum buffer length
1367 {
1368 var reader_buf: [Dir.Reader.min_buffer_len]u8 align(@alignOf(usize)) = undefined;
1369 var reader: Dir.Reader = .init(iterable_dir, &reader_buf);
1370
1371 var file_count: usize = 0;
1372 var dir_count: usize = 0;
1373 while (try reader.next(io)) |entry| {
1374 switch (entry.kind) {
1375 .file => {
1376 try expectEqualStrings(maxed_filename, entry.name);
1377 file_count += 1;
1378 },
1379 .directory => {
1380 try expectEqualStrings(maxed_dirname, entry.name);
1381 dir_count += 1;
1382 },
1383 else => return error.TestFailed,
1384 }
1385 }
1386 try expectEqual(@as(usize, 1), file_count);
1387 try expectEqual(@as(usize, 1), dir_count);
1388 }
1389 // High level walk API
1390 {
13511391 var walker = try iterable_dir.walk(testing.allocator);
13521392 defer walker.deinit();
13531393
1354 var count: usize = 0;
1355 while (try walker.next()) |entry| {
1356 try testing.expectEqualStrings(maxed_filename, entry.basename);
1357 count += 1;
1394 var file_count: usize = 0;
1395 var dir_count: usize = 0;
1396 while (try walker.next(io)) |entry| {
1397 switch (entry.kind) {
1398 .file => {
1399 try expectEqualStrings(maxed_filename, entry.basename);
1400 file_count += 1;
1401 },
1402 .directory => {
1403 try expectEqualStrings(maxed_dirname, entry.basename);
1404 dir_count += 1;
1405 },
1406 else => return error.TestFailed,
1407 }
13581408 }
1359 try testing.expectEqual(@as(usize, 2), count);
1409 try expectEqual(@as(usize, 2), file_count);
1410 try expectEqual(@as(usize, 1), dir_count);
13601411 }
13611412
13621413 // ensure that we can delete the tree
1363 try iterable_dir.deleteTree(maxed_filename);
1414 try iterable_dir.deleteTree(io, maxed_filename);
13641415}
13651416
13661417test "max file name component lengths" {
1418 const io = testing.io;
1419
13671420 var tmp = tmpDir(.{ .iterate = true });
13681421 defer tmp.cleanup();
13691422
13701423 if (native_os == .windows) {
13711424 // U+FFFF is the character with the largest code point that is encoded as a single
1372 // UTF-16 code unit, so Windows allows for NAME_MAX of them.
1373 const maxed_windows_filename = ("\u{FFFF}".*) ** windows.NAME_MAX;
1374 try testFilenameLimits(tmp.dir, &maxed_windows_filename);
1425 // WTF-16 code unit, so Windows allows for NAME_MAX of them.
1426 const maxed_windows_filename1 = ("\u{FFFF}".*) ** windows.NAME_MAX;
1427 // This is also a code point that is encoded as one WTF-16 code unit, but
1428 // three WTF-8 bytes, so it exercises the limits of both WTF-16 and WTF-8 encodings.
1429 const maxed_windows_filename2 = ("€".*) ** windows.NAME_MAX;
1430 try testFilenameLimits(io, tmp.dir, &maxed_windows_filename1, &maxed_windows_filename2);
13751431 } else if (native_os == .wasi) {
13761432 // On WASI, the maxed filename depends on the host OS, so in order for this test to
13771433 // work on any host, we need to use a length that will work for all platforms
13781434 // (i.e. the minimum max_name_bytes of all supported platforms).
1379 const maxed_wasi_filename = [_]u8{'1'} ** 255;
1380 try testFilenameLimits(tmp.dir, &maxed_wasi_filename);
1435 const maxed_wasi_filename1: [255]u8 = @splat('1');
1436 const maxed_wasi_filename2: [255]u8 = @splat('2');
1437 try testFilenameLimits(io, tmp.dir, &maxed_wasi_filename1, &maxed_wasi_filename2);
13811438 } else {
1382 const maxed_ascii_filename = [_]u8{'1'} ** std.fs.max_name_bytes;
1383 try testFilenameLimits(tmp.dir, &maxed_ascii_filename);
1439 const maxed_ascii_filename1: [Dir.max_name_bytes]u8 = @splat('1');
1440 const maxed_ascii_filename2: [Dir.max_name_bytes]u8 = @splat('2');
1441 try testFilenameLimits(io, tmp.dir, &maxed_ascii_filename1, &maxed_ascii_filename2);
13841442 }
13851443}
13861444
......@@ -1398,21 +1456,21 @@ test "writev, readv" {
13981456 var write_vecs: [2][]const u8 = .{ line1, line2 };
13991457 var read_vecs: [2][]u8 = .{ &buf2, &buf1 };
14001458
1401 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
1402 defer src_file.close();
1459 var src_file = try tmp.dir.createFile(io, "test.txt", .{ .read = true });
1460 defer src_file.close(io);
14031461
1404 var writer = src_file.writerStreaming(&.{});
1462 var writer = src_file.writerStreaming(io, &.{});
14051463
14061464 try writer.interface.writeVecAll(&write_vecs);
14071465 try writer.interface.flush();
1408 try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.getEndPos());
1466 try expectEqual(@as(u64, line1.len + line2.len), try src_file.length(io));
14091467
1410 var reader = writer.moveToReader(io);
1468 var reader = writer.moveToReader();
14111469 try reader.seekTo(0);
14121470 try reader.interface.readVecAll(&read_vecs);
1413 try testing.expectEqualStrings(&buf1, "line2\n");
1414 try testing.expectEqualStrings(&buf2, "line1\n");
1415 try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
1471 try expectEqualStrings(&buf1, "line2\n");
1472 try expectEqualStrings(&buf2, "line1\n");
1473 try expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
14161474}
14171475
14181476test "pwritev, preadv" {
......@@ -1428,87 +1486,37 @@ test "pwritev, preadv" {
14281486 var buf2: [line2.len]u8 = undefined;
14291487 var read_vecs: [2][]u8 = .{ &buf2, &buf1 };
14301488
1431 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
1432 defer src_file.close();
1489 var src_file = try tmp.dir.createFile(io, "test.txt", .{ .read = true });
1490 defer src_file.close(io);
14331491
1434 var writer = src_file.writer(&.{});
1492 var writer = src_file.writer(io, &.{});
14351493
14361494 try writer.seekTo(16);
14371495 try writer.interface.writeVecAll(&lines);
14381496 try writer.interface.flush();
1439 try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.getEndPos());
1497 try expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.length(io));
14401498
1441 var reader = writer.moveToReader(io);
1499 var reader = writer.moveToReader();
14421500 try reader.seekTo(16);
14431501 try reader.interface.readVecAll(&read_vecs);
1444 try testing.expectEqualStrings(&buf1, "line2\n");
1445 try testing.expectEqualStrings(&buf2, "line1\n");
1446 try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
1447}
1448
1449test "setEndPos" {
1450 // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission)
1451 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1452 if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23806
1453
1454 const io = testing.io;
1455
1456 var tmp = tmpDir(.{});
1457 defer tmp.cleanup();
1458
1459 const file_name = "afile.txt";
1460 try tmp.dir.writeFile(.{ .sub_path = file_name, .data = "ninebytes" });
1461 const f = try tmp.dir.openFile(file_name, .{ .mode = .read_write });
1462 defer f.close();
1463
1464 const initial_size = try f.getEndPos();
1465 var buffer: [32]u8 = undefined;
1466 var reader = f.reader(io, &.{});
1467
1468 {
1469 try f.setEndPos(initial_size);
1470 try testing.expectEqual(initial_size, try f.getEndPos());
1471 try reader.seekTo(0);
1472 try testing.expectEqual(initial_size, try reader.interface.readSliceShort(&buffer));
1473 try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]);
1474 }
1475
1476 {
1477 const larger = initial_size + 4;
1478 try f.setEndPos(larger);
1479 try testing.expectEqual(larger, try f.getEndPos());
1480 try reader.seekTo(0);
1481 try testing.expectEqual(larger, try reader.interface.readSliceShort(&buffer));
1482 try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]);
1483 }
1484
1485 {
1486 const smaller = initial_size - 5;
1487 try f.setEndPos(smaller);
1488 try testing.expectEqual(smaller, try f.getEndPos());
1489 try reader.seekTo(0);
1490 try testing.expectEqual(smaller, try reader.interface.readSliceShort(&buffer));
1491 try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]);
1492 }
1493
1494 try f.setEndPos(0);
1495 try testing.expectEqual(0, try f.getEndPos());
1496 try reader.seekTo(0);
1497 try testing.expectEqual(0, try reader.interface.readSliceShort(&buffer));
1502 try expectEqualStrings(&buf1, "line2\n");
1503 try expectEqualStrings(&buf2, "line1\n");
1504 try expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
14981505}
14991506
15001507test "access file" {
15011508 try testWithAllSupportedPathTypes(struct {
15021509 fn impl(ctx: *TestContext) !void {
1510 const io = ctx.io;
15031511 const dir_path = try ctx.transformPath("os_test_tmp");
1504 const file_path = try ctx.transformPath("os_test_tmp" ++ fs.path.sep_str ++ "file.txt");
1512 const file_path = try ctx.transformPath("os_test_tmp" ++ Dir.path.sep_str ++ "file.txt");
15051513
1506 try ctx.dir.makePath(dir_path);
1507 try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{}));
1514 try ctx.dir.createDirPath(io, dir_path);
1515 try expectError(error.FileNotFound, ctx.dir.access(io, file_path, .{}));
15081516
1509 try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "" });
1510 try ctx.dir.access(file_path, .{});
1511 try ctx.dir.deleteTree(dir_path);
1517 try ctx.dir.writeFile(io, .{ .sub_path = file_path, .data = "" });
1518 try ctx.dir.access(io, file_path, .{});
1519 try ctx.dir.deleteTree(io, dir_path);
15121520 }
15131521 }.impl);
15141522}
......@@ -1519,24 +1527,24 @@ test "sendfile" {
15191527 var tmp = tmpDir(.{});
15201528 defer tmp.cleanup();
15211529
1522 try tmp.dir.makePath("os_test_tmp");
1530 try tmp.dir.createDirPath(io, "os_test_tmp");
15231531
1524 var dir = try tmp.dir.openDir("os_test_tmp", .{});
1525 defer dir.close();
1532 var dir = try tmp.dir.openDir(io, "os_test_tmp", .{});
1533 defer dir.close(io);
15261534
15271535 const line1 = "line1\n";
15281536 const line2 = "second line\n";
15291537 var vecs = [_][]const u8{ line1, line2 };
15301538
1531 var src_file = try dir.createFile("sendfile1.txt", .{ .read = true });
1532 defer src_file.close();
1539 var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true });
1540 defer src_file.close(io);
15331541 {
1534 var fw = src_file.writer(&.{});
1542 var fw = src_file.writer(io, &.{});
15351543 try fw.interface.writeVecAll(&vecs);
15361544 }
15371545
1538 var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true });
1539 defer dest_file.close();
1546 var dest_file = try dir.createFile(io, "sendfile2.txt", .{ .read = true });
1547 defer dest_file.close(io);
15401548
15411549 const header1 = "header1\n";
15421550 const header2 = "second header\n";
......@@ -1548,16 +1556,16 @@ test "sendfile" {
15481556 var written_buf: [100]u8 = undefined;
15491557 var file_reader = src_file.reader(io, &.{});
15501558 var fallback_buffer: [50]u8 = undefined;
1551 var file_writer = dest_file.writer(&fallback_buffer);
1559 var file_writer = dest_file.writer(io, &fallback_buffer);
15521560 try file_writer.interface.writeVecAll(&headers);
15531561 try file_reader.seekTo(1);
1554 try testing.expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10)));
1562 try expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10)));
15551563 try file_writer.interface.writeVecAll(&trailers);
15561564 try file_writer.interface.flush();
1557 var fr = file_writer.moveToReader(io);
1565 var fr = file_writer.moveToReader();
15581566 try fr.seekTo(0);
15591567 const amt = try fr.interface.readSliceShort(&written_buf);
1560 try testing.expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]);
1568 try expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]);
15611569}
15621570
15631571test "sendfile with buffered data" {
......@@ -1566,18 +1574,18 @@ test "sendfile with buffered data" {
15661574 var tmp = tmpDir(.{});
15671575 defer tmp.cleanup();
15681576
1569 try tmp.dir.makePath("os_test_tmp");
1577 try tmp.dir.createDirPath(io, "os_test_tmp");
15701578
1571 var dir = try tmp.dir.openDir("os_test_tmp", .{});
1572 defer dir.close();
1579 var dir = try tmp.dir.openDir(io, "os_test_tmp", .{});
1580 defer dir.close(io);
15731581
1574 var src_file = try dir.createFile("sendfile1.txt", .{ .read = true });
1575 defer src_file.close();
1582 var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true });
1583 defer src_file.close(io);
15761584
1577 try src_file.writeAll("AAAABBBB");
1585 try src_file.writeStreamingAll(io, "AAAABBBB");
15781586
1579 var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true });
1580 defer dest_file.close();
1587 var dest_file = try dir.createFile(io, "sendfile2.txt", .{ .read = true });
1588 defer dest_file.close(io);
15811589
15821590 var src_buffer: [32]u8 = undefined;
15831591 var file_reader = src_file.reader(io, &src_buffer);
......@@ -1586,52 +1594,54 @@ test "sendfile with buffered data" {
15861594 try file_reader.interface.fill(8);
15871595
15881596 var fallback_buffer: [32]u8 = undefined;
1589 var file_writer = dest_file.writer(&fallback_buffer);
1597 var file_writer = dest_file.writer(io, &fallback_buffer);
15901598
1591 try std.testing.expectEqual(4, try file_writer.interface.sendFileAll(&file_reader, .limited(4)));
1599 try expectEqual(4, try file_writer.interface.sendFileAll(&file_reader, .limited(4)));
15921600
15931601 var written_buf: [8]u8 = undefined;
1594 var fr = file_writer.moveToReader(io);
1602 var fr = file_writer.moveToReader();
15951603 try fr.seekTo(0);
15961604 const amt = try fr.interface.readSliceShort(&written_buf);
15971605
1598 try std.testing.expectEqual(4, amt);
1599 try std.testing.expectEqualSlices(u8, "AAAA", written_buf[0..amt]);
1606 try expectEqual(4, amt);
1607 try expectEqualSlices(u8, "AAAA", written_buf[0..amt]);
16001608}
16011609
16021610test "copyFile" {
16031611 try testWithAllSupportedPathTypes(struct {
16041612 fn impl(ctx: *TestContext) !void {
1613 const io = ctx.io;
16051614 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
16061615 const src_file = try ctx.transformPath("tmp_test_copy_file.txt");
16071616 const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt");
16081617 const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt");
16091618
1610 try ctx.dir.writeFile(.{ .sub_path = src_file, .data = data });
1611 defer ctx.dir.deleteFile(src_file) catch {};
1619 try ctx.dir.writeFile(io, .{ .sub_path = src_file, .data = data });
1620 defer ctx.dir.deleteFile(io, src_file) catch {};
16121621
1613 try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{});
1614 defer ctx.dir.deleteFile(dest_file) catch {};
1622 try ctx.dir.copyFile(src_file, ctx.dir, dest_file, io, .{});
1623 defer ctx.dir.deleteFile(io, dest_file) catch {};
16151624
1616 try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, .{ .override_mode = File.default_mode });
1617 defer ctx.dir.deleteFile(dest_file2) catch {};
1625 try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, io, .{});
1626 defer ctx.dir.deleteFile(io, dest_file2) catch {};
16181627
1619 try expectFileContents(ctx.dir, dest_file, data);
1620 try expectFileContents(ctx.dir, dest_file2, data);
1628 try expectFileContents(io, ctx.dir, dest_file, data);
1629 try expectFileContents(io, ctx.dir, dest_file2, data);
16211630 }
16221631 }.impl);
16231632}
16241633
1625fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
1626 const contents = try dir.readFileAlloc(file_path, testing.allocator, .limited(1000));
1634fn expectFileContents(io: Io, dir: Dir, file_path: []const u8, data: []const u8) !void {
1635 const contents = try dir.readFileAlloc(io, file_path, testing.allocator, .limited(1000));
16271636 defer testing.allocator.free(contents);
16281637
1629 try testing.expectEqualSlices(u8, data, contents);
1638 try expectEqualSlices(u8, data, contents);
16301639}
16311640
16321641test "AtomicFile" {
16331642 try testWithAllSupportedPathTypes(struct {
16341643 fn impl(ctx: *TestContext) !void {
1644 const io = ctx.io;
16351645 const allocator = ctx.arena.allocator();
16361646 const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt");
16371647 const test_content =
......@@ -1641,15 +1651,15 @@ test "AtomicFile" {
16411651
16421652 {
16431653 var buffer: [100]u8 = undefined;
1644 var af = try ctx.dir.atomicFile(test_out_file, .{ .write_buffer = &buffer });
1654 var af = try ctx.dir.atomicFile(io, test_out_file, .{ .write_buffer = &buffer });
16451655 defer af.deinit();
16461656 try af.file_writer.interface.writeAll(test_content);
16471657 try af.finish();
16481658 }
1649 const content = try ctx.dir.readFileAlloc(test_out_file, allocator, .limited(9999));
1650 try testing.expectEqualStrings(test_content, content);
1659 const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999));
1660 try expectEqualStrings(test_content, content);
16511661
1652 try ctx.dir.deleteFile(test_out_file);
1662 try ctx.dir.deleteFile(io, test_out_file);
16531663 }
16541664 }.impl);
16551665}
......@@ -1659,13 +1669,14 @@ test "open file with exclusive nonblocking lock twice" {
16591669
16601670 try testWithAllSupportedPathTypes(struct {
16611671 fn impl(ctx: *TestContext) !void {
1672 const io = ctx.io;
16621673 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
16631674
1664 const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1665 defer file1.close();
1675 const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1676 defer file1.close(io);
16661677
1667 const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1668 try testing.expectError(error.WouldBlock, file2);
1678 const file2 = ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1679 try expectError(error.WouldBlock, file2);
16691680 }
16701681 }.impl);
16711682}
......@@ -1675,13 +1686,14 @@ test "open file with shared and exclusive nonblocking lock" {
16751686
16761687 try testWithAllSupportedPathTypes(struct {
16771688 fn impl(ctx: *TestContext) !void {
1689 const io = ctx.io;
16781690 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
16791691
1680 const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
1681 defer file1.close();
1692 const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .shared, .lock_nonblocking = true });
1693 defer file1.close(io);
16821694
1683 const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1684 try testing.expectError(error.WouldBlock, file2);
1695 const file2 = ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1696 try expectError(error.WouldBlock, file2);
16851697 }
16861698 }.impl);
16871699}
......@@ -1691,13 +1703,14 @@ test "open file with exclusive and shared nonblocking lock" {
16911703
16921704 try testWithAllSupportedPathTypes(struct {
16931705 fn impl(ctx: *TestContext) !void {
1706 const io = ctx.io;
16941707 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
16951708
1696 const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1697 defer file1.close();
1709 const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1710 defer file1.close(io);
16981711
1699 const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });
1700 try testing.expectError(error.WouldBlock, file2);
1712 const file2 = ctx.dir.createFile(io, filename, .{ .lock = .shared, .lock_nonblocking = true });
1713 try expectError(error.WouldBlock, file2);
17011714 }
17021715 }.impl);
17031716}
......@@ -1707,39 +1720,35 @@ test "open file with exclusive lock twice, make sure second lock waits" {
17071720
17081721 try testWithAllSupportedPathTypes(struct {
17091722 fn impl(ctx: *TestContext) !void {
1723 const io = ctx.io;
17101724 const filename = try ctx.transformPath("file_lock_test.txt");
17111725
1712 const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive });
1713 errdefer file.close();
1726 const file = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive });
1727 errdefer file.close(io);
17141728
17151729 const S = struct {
1716 fn checkFn(dir: *fs.Dir, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
1730 fn checkFn(inner_ctx: *TestContext, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void {
17171731 started.set();
1718 const file1 = try dir.createFile(path, .{ .lock = .exclusive });
1732 const file1 = try inner_ctx.dir.createFile(inner_ctx.io, path, .{ .lock = .exclusive });
17191733
17201734 locked.set();
1721 file1.close();
1735 file1.close(inner_ctx.io);
17221736 }
17231737 };
17241738
17251739 var started: std.Thread.ResetEvent = .unset;
17261740 var locked: std.Thread.ResetEvent = .unset;
17271741
1728 const t = try std.Thread.spawn(.{}, S.checkFn, .{
1729 &ctx.dir,
1730 filename,
1731 &started,
1732 &locked,
1733 });
1742 const t = try std.Thread.spawn(.{}, S.checkFn, .{ ctx, filename, &started, &locked });
17341743 defer t.join();
17351744
17361745 // Wait for the spawned thread to start trying to acquire the exclusive file lock.
17371746 // Then wait a bit to make sure that can't acquire it since we currently hold the file lock.
17381747 started.wait();
1739 try testing.expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms));
1748 try expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms));
17401749
17411750 // Release the file lock which should unlock the thread to lock it and set the locked event.
1742 file.close();
1751 file.close(io);
17431752 locked.wait();
17441753 }
17451754 }.impl);
......@@ -1748,11 +1757,13 @@ test "open file with exclusive lock twice, make sure second lock waits" {
17481757test "open file with exclusive nonblocking lock twice (absolute paths)" {
17491758 if (native_os == .wasi) return error.SkipZigTest;
17501759
1760 const io = testing.io;
1761
17511762 var random_bytes: [12]u8 = undefined;
17521763 std.crypto.random.bytes(&random_bytes);
17531764
1754 var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
1755 _ = fs.base64_encoder.encode(&random_b64, &random_bytes);
1765 var random_b64: [std.fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
1766 _ = std.fs.base64_encoder.encode(&random_b64, &random_bytes);
17561767
17571768 const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt";
17581769
......@@ -1761,47 +1772,50 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
17611772 const cwd = try std.process.getCwdAlloc(gpa);
17621773 defer gpa.free(cwd);
17631774
1764 const filename = try fs.path.resolve(gpa, &.{ cwd, sub_path });
1775 const filename = try Dir.path.resolve(gpa, &.{ cwd, sub_path });
17651776 defer gpa.free(filename);
17661777
1767 defer fs.deleteFileAbsolute(filename) catch {}; // createFileAbsolute can leave files on failures
1768 const file1 = try fs.createFileAbsolute(filename, .{
1778 defer Dir.deleteFileAbsolute(io, filename) catch {}; // createFileAbsolute can leave files on failures
1779 const file1 = try Dir.createFileAbsolute(io, filename, .{
17691780 .lock = .exclusive,
17701781 .lock_nonblocking = true,
17711782 });
17721783
1773 const file2 = fs.createFileAbsolute(filename, .{
1784 const file2 = Dir.createFileAbsolute(io, filename, .{
17741785 .lock = .exclusive,
17751786 .lock_nonblocking = true,
17761787 });
1777 file1.close();
1778 try testing.expectError(error.WouldBlock, file2);
1788 file1.close(io);
1789 try expectError(error.WouldBlock, file2);
17791790}
17801791
17811792test "read from locked file" {
17821793 try testWithAllSupportedPathTypes(struct {
17831794 fn impl(ctx: *TestContext) !void {
1795 const io = ctx.io;
17841796 const filename = try ctx.transformPath("read_lock_file_test.txt");
17851797
17861798 {
1787 const f = try ctx.dir.createFile(filename, .{ .read = true });
1788 defer f.close();
1799 const f = try ctx.dir.createFile(io, filename, .{ .read = true });
1800 defer f.close(io);
17891801 var buffer: [1]u8 = undefined;
1790 _ = try f.read(&buffer);
1802 _ = try f.readPositional(io, &.{&buffer}, 0);
17911803 }
17921804 {
1793 const f = try ctx.dir.createFile(filename, .{
1805 const f = try ctx.dir.createFile(io, filename, .{
17941806 .read = true,
17951807 .lock = .exclusive,
17961808 });
1797 defer f.close();
1798 const f2 = try ctx.dir.openFile(filename, .{});
1799 defer f2.close();
1809 defer f.close(io);
1810 const f2 = try ctx.dir.openFile(io, filename, .{});
1811 defer f2.close(io);
1812 // On POSIX locks may be ignored, however on Windows they cause
1813 // LockViolation.
18001814 var buffer: [1]u8 = undefined;
18011815 if (builtin.os.tag == .windows) {
1802 try std.testing.expectError(error.LockViolation, f2.read(&buffer));
1816 try expectError(error.LockViolation, f2.readPositional(io, &.{&buffer}, 0));
18031817 } else {
1804 try std.testing.expectEqual(0, f2.read(&buffer));
1818 try expectEqual(0, f2.readPositional(io, &.{&buffer}, 0));
18051819 }
18061820 }
18071821 }
......@@ -1809,6 +1823,8 @@ test "read from locked file" {
18091823}
18101824
18111825test "walker" {
1826 const io = testing.io;
1827
18121828 var tmp = tmpDir(.{ .iterate = true });
18131829 defer tmp.cleanup();
18141830
......@@ -1819,9 +1835,9 @@ test "walker" {
18191835 .{ "dir2", 1 },
18201836 .{ "dir3", 1 },
18211837 .{ "dir4", 1 },
1822 .{ "dir3" ++ fs.path.sep_str ++ "sub1", 2 },
1823 .{ "dir3" ++ fs.path.sep_str ++ "sub2", 2 },
1824 .{ "dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1", 3 },
1838 .{ "dir3" ++ Dir.path.sep_str ++ "sub1", 2 },
1839 .{ "dir3" ++ Dir.path.sep_str ++ "sub2", 2 },
1840 .{ "dir3" ++ Dir.path.sep_str ++ "sub2" ++ Dir.path.sep_str ++ "subsub1", 3 },
18251841 });
18261842
18271843 const expected_basenames = std.StaticStringMap(void).initComptime(.{
......@@ -1835,35 +1851,37 @@ test "walker" {
18351851 });
18361852
18371853 for (expected_paths.keys()) |key| {
1838 try tmp.dir.makePath(key);
1854 try tmp.dir.createDirPath(io, key);
18391855 }
18401856
18411857 var walker = try tmp.dir.walk(testing.allocator);
18421858 defer walker.deinit();
18431859
18441860 var num_walked: usize = 0;
1845 while (try walker.next()) |entry| {
1846 testing.expect(expected_basenames.has(entry.basename)) catch |err| {
1861 while (try walker.next(io)) |entry| {
1862 expect(expected_basenames.has(entry.basename)) catch |err| {
18471863 std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)});
18481864 return err;
18491865 };
1850 testing.expect(expected_paths.has(entry.path)) catch |err| {
1866 expect(expected_paths.has(entry.path)) catch |err| {
18511867 std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
18521868 return err;
18531869 };
1854 testing.expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| {
1870 expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| {
18551871 std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
18561872 return err;
18571873 };
18581874 // make sure that the entry.dir is the containing dir
1859 var entry_dir = try entry.dir.openDir(entry.basename, .{});
1860 defer entry_dir.close();
1875 var entry_dir = try entry.dir.openDir(io, entry.basename, .{});
1876 defer entry_dir.close(io);
18611877 num_walked += 1;
18621878 }
1863 try testing.expectEqual(expected_paths.kvs.len, num_walked);
1879 try expectEqual(expected_paths.kvs.len, num_walked);
18641880}
18651881
18661882test "selective walker, skip entries that start with ." {
1883 const io = testing.io;
1884
18671885 var tmp = tmpDir(.{ .iterate = true });
18681886 defer tmp.cleanup();
18691887
......@@ -1878,11 +1896,11 @@ test "selective walker, skip entries that start with ." {
18781896
18791897 const expected_paths = std.StaticStringMap(usize).initComptime(.{
18801898 .{ "dir1", 1 },
1881 .{ "dir1" ++ fs.path.sep_str ++ "foo", 2 },
1899 .{ "dir1" ++ Dir.path.sep_str ++ "foo", 2 },
18821900 .{ "a", 1 },
1883 .{ "a" ++ fs.path.sep_str ++ "b", 2 },
1884 .{ "a" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c", 3 },
1885 .{ "a" ++ fs.path.sep_str ++ "baz", 2 },
1901 .{ "a" ++ Dir.path.sep_str ++ "b", 2 },
1902 .{ "a" ++ Dir.path.sep_str ++ "b" ++ Dir.path.sep_str ++ "c", 3 },
1903 .{ "a" ++ Dir.path.sep_str ++ "baz", 2 },
18861904 });
18871905
18881906 const expected_basenames = std.StaticStringMap(void).initComptime(.{
......@@ -1895,41 +1913,43 @@ test "selective walker, skip entries that start with ." {
18951913 });
18961914
18971915 for (paths_to_create) |path| {
1898 try tmp.dir.makePath(path);
1916 try tmp.dir.createDirPath(io, path);
18991917 }
19001918
19011919 var walker = try tmp.dir.walkSelectively(testing.allocator);
19021920 defer walker.deinit();
19031921
19041922 var num_walked: usize = 0;
1905 while (try walker.next()) |entry| {
1923 while (try walker.next(io)) |entry| {
19061924 if (entry.basename[0] == '.') continue;
19071925 if (entry.kind == .directory) {
1908 try walker.enter(entry);
1926 try walker.enter(io, entry);
19091927 }
19101928
1911 testing.expect(expected_basenames.has(entry.basename)) catch |err| {
1929 expect(expected_basenames.has(entry.basename)) catch |err| {
19121930 std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)});
19131931 return err;
19141932 };
1915 testing.expect(expected_paths.has(entry.path)) catch |err| {
1933 expect(expected_paths.has(entry.path)) catch |err| {
19161934 std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
19171935 return err;
19181936 };
1919 testing.expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| {
1937 expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| {
19201938 std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
19211939 return err;
19221940 };
19231941
19241942 // make sure that the entry.dir is the containing dir
1925 var entry_dir = try entry.dir.openDir(entry.basename, .{});
1926 defer entry_dir.close();
1943 var entry_dir = try entry.dir.openDir(io, entry.basename, .{});
1944 defer entry_dir.close(io);
19271945 num_walked += 1;
19281946 }
1929 try testing.expectEqual(expected_paths.kvs.len, num_walked);
1947 try expectEqual(expected_paths.kvs.len, num_walked);
19301948}
19311949
19321950test "walker without fully iterating" {
1951 const io = testing.io;
1952
19331953 var tmp = tmpDir(.{ .iterate = true });
19341954 defer tmp.cleanup();
19351955
......@@ -1939,18 +1959,18 @@ test "walker without fully iterating" {
19391959 // Create 2 directories inside the tmp directory, but then only iterate once before breaking.
19401960 // This ensures that walker doesn't try to close the initial directory when not fully iterating.
19411961
1942 try tmp.dir.makePath("a");
1943 try tmp.dir.makePath("b");
1962 try tmp.dir.createDirPath(io, "a");
1963 try tmp.dir.createDirPath(io, "b");
19441964
19451965 var num_walked: usize = 0;
1946 while (try walker.next()) |_| {
1966 while (try walker.next(io)) |_| {
19471967 num_walked += 1;
19481968 break;
19491969 }
1950 try testing.expectEqual(@as(usize, 1), num_walked);
1970 try expectEqual(@as(usize, 1), num_walked);
19511971}
19521972
1953test "'.' and '..' in fs.Dir functions" {
1973test "'.' and '..' in Dir functions" {
19541974 if (native_os == .windows and builtin.cpu.arch == .aarch64) {
19551975 // https://github.com/ziglang/zig/issues/17134
19561976 return error.SkipZigTest;
......@@ -1965,27 +1985,27 @@ test "'.' and '..' in fs.Dir functions" {
19651985 const rename_path = try ctx.transformPath("./subdir/../rename");
19661986 const update_path = try ctx.transformPath("./subdir/../update");
19671987
1968 try ctx.dir.makeDir(subdir_path);
1969 try ctx.dir.access(subdir_path, .{});
1970 var created_subdir = try ctx.dir.openDir(subdir_path, .{});
1971 created_subdir.close();
1988 try ctx.dir.createDir(io, subdir_path, .default_dir);
1989 try ctx.dir.access(io, subdir_path, .{});
1990 var created_subdir = try ctx.dir.openDir(io, subdir_path, .{});
1991 created_subdir.close(io);
19721992
1973 const created_file = try ctx.dir.createFile(file_path, .{});
1974 created_file.close();
1975 try ctx.dir.access(file_path, .{});
1993 const created_file = try ctx.dir.createFile(io, file_path, .{});
1994 created_file.close(io);
1995 try ctx.dir.access(io, file_path, .{});
19761996
1977 try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{});
1978 try ctx.dir.rename(copy_path, rename_path);
1979 const renamed_file = try ctx.dir.openFile(rename_path, .{});
1980 renamed_file.close();
1981 try ctx.dir.deleteFile(rename_path);
1997 try ctx.dir.copyFile(file_path, ctx.dir, copy_path, io, .{});
1998 try ctx.dir.rename(copy_path, ctx.dir, rename_path, io);
1999 const renamed_file = try ctx.dir.openFile(io, rename_path, .{});
2000 renamed_file.close(io);
2001 try ctx.dir.deleteFile(io, rename_path);
19822002
1983 try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" });
1984 var dir = ctx.dir.adaptToNewApi();
2003 try ctx.dir.writeFile(io, .{ .sub_path = update_path, .data = "something" });
2004 var dir = ctx.dir;
19852005 const prev_status = try dir.updateFile(io, file_path, dir, update_path, .{});
1986 try testing.expectEqual(Io.Dir.PrevStatus.stale, prev_status);
2006 try expectEqual(Dir.PrevStatus.stale, prev_status);
19872007
1988 try ctx.dir.deleteDir(subdir_path);
2008 try ctx.dir.deleteDir(io, subdir_path);
19892009 }
19902010 }.impl);
19912011}
......@@ -1994,6 +2014,8 @@ test "'.' and '..' in absolute functions" {
19942014 if (native_os == .wasi) return error.SkipZigTest;
19952015 if (native_os == .openbsd) return error.SkipZigTest;
19962016
2017 const io = testing.io;
2018
19972019 var tmp = tmpDir(.{});
19982020 defer tmp.cleanup();
19992021
......@@ -2001,83 +2023,71 @@ test "'.' and '..' in absolute functions" {
20012023 defer arena.deinit();
20022024 const allocator = arena.allocator();
20032025
2004 const base_path = try tmp.dir.realpathAlloc(allocator, ".");
2026 const base_path = try tmp.dir.realPathFileAlloc(io, ".", allocator);
20052027
2006 const subdir_path = try fs.path.join(allocator, &.{ base_path, "./subdir" });
2007 try fs.makeDirAbsolute(subdir_path);
2008 try fs.accessAbsolute(subdir_path, .{});
2009 var created_subdir = try fs.openDirAbsolute(subdir_path, .{});
2010 created_subdir.close();
2028 const subdir_path = try Dir.path.join(allocator, &.{ base_path, "./subdir" });
2029 try Dir.createDirAbsolute(io, subdir_path, .default_dir);
2030 try Dir.accessAbsolute(io, subdir_path, .{});
2031 var created_subdir = try Dir.openDirAbsolute(io, subdir_path, .{});
2032 created_subdir.close(io);
20112033
2012 const created_file_path = try fs.path.join(allocator, &.{ subdir_path, "../file" });
2013 const created_file = try fs.createFileAbsolute(created_file_path, .{});
2014 created_file.close();
2015 try fs.accessAbsolute(created_file_path, .{});
2034 const created_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../file" });
2035 const created_file = try Dir.createFileAbsolute(io, created_file_path, .{});
2036 created_file.close(io);
2037 try Dir.accessAbsolute(io, created_file_path, .{});
20162038
2017 const copied_file_path = try fs.path.join(allocator, &.{ subdir_path, "../copy" });
2018 try fs.copyFileAbsolute(created_file_path, copied_file_path, .{});
2019 const renamed_file_path = try fs.path.join(allocator, &.{ subdir_path, "../rename" });
2020 try fs.renameAbsolute(copied_file_path, renamed_file_path);
2021 const renamed_file = try fs.openFileAbsolute(renamed_file_path, .{});
2022 renamed_file.close();
2023 try fs.deleteFileAbsolute(renamed_file_path);
2039 const copied_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../copy" });
2040 try Dir.copyFileAbsolute(created_file_path, copied_file_path, io, .{});
2041 const renamed_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../rename" });
2042 try Dir.renameAbsolute(copied_file_path, renamed_file_path, io);
2043 const renamed_file = try Dir.openFileAbsolute(io, renamed_file_path, .{});
2044 renamed_file.close(io);
2045 try Dir.deleteFileAbsolute(io, renamed_file_path);
20242046
2025 try fs.deleteDirAbsolute(subdir_path);
2047 try Dir.deleteDirAbsolute(io, subdir_path);
20262048}
20272049
20282050test "chmod" {
2029 if (native_os == .windows or native_os == .wasi)
2030 return error.SkipZigTest;
2051 if (native_os == .windows or native_os == .wasi) return;
2052
2053 const io = testing.io;
20312054
20322055 var tmp = tmpDir(.{});
20332056 defer tmp.cleanup();
20342057
2035 const file = try tmp.dir.createFile("test_file", .{ .mode = 0o600 });
2036 defer file.close();
2037 try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat()).mode & 0o7777);
2058 const file = try tmp.dir.createFile(io, "test_file", .{ .permissions = .fromMode(0o600) });
2059 defer file.close(io);
2060 try expectEqual(0o600, (try file.stat(io)).permissions.toMode() & 0o7777);
20382061
2039 try file.chmod(0o644);
2040 try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777);
2062 try file.setPermissions(io, .fromMode(0o644));
2063 try expectEqual(0o644, (try file.stat(io)).permissions.toMode() & 0o7777);
20412064
2042 try tmp.dir.makeDir("test_dir");
2043 var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
2044 defer dir.close();
2065 try tmp.dir.createDir(io, "test_dir", .default_dir);
2066 var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true });
2067 defer dir.close(io);
20452068
2046 try dir.chmod(0o700);
2047 try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777);
2069 try dir.setPermissions(io, .fromMode(0o700));
2070 try expectEqual(0o700, (try dir.stat(io)).permissions.toMode() & 0o7777);
20482071}
20492072
2050test "chown" {
2073test "change ownership" {
20512074 if (native_os == .windows or native_os == .wasi)
20522075 return error.SkipZigTest;
20532076
2077 const io = testing.io;
2078
20542079 var tmp = tmpDir(.{});
20552080 defer tmp.cleanup();
20562081
2057 const file = try tmp.dir.createFile("test_file", .{});
2058 defer file.close();
2059 try file.chown(null, null);
2082 const file = try tmp.dir.createFile(io, "test_file", .{});
2083 defer file.close(io);
2084 try file.setOwner(io, null, null);
20602085
2061 try tmp.dir.makeDir("test_dir");
2086 try tmp.dir.createDir(io, "test_dir", .default_dir);
20622087
2063 var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
2064 defer dir.close();
2065 try dir.chown(null, null);
2066}
2067
2068test "delete a setAsCwd directory on Windows" {
2069 if (native_os != .windows) return error.SkipZigTest;
2070
2071 var tmp = tmpDir(.{});
2072 // Set tmp dir as current working directory.
2073 try tmp.dir.setAsCwd();
2074 tmp.dir.close();
2075 try testing.expectError(error.FileBusy, tmp.parent_dir.deleteTree(&tmp.sub_path));
2076 // Now set the parent dir as the current working dir for clean up.
2077 try tmp.parent_dir.setAsCwd();
2078 try tmp.parent_dir.deleteTree(&tmp.sub_path);
2079 // Close the parent "tmp" so we don't leak the HANDLE.
2080 tmp.parent_dir.close();
2088 var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true });
2089 defer dir.close(io);
2090 try dir.setOwner(io, null, null);
20812091}
20822092
20832093test "invalid UTF-8/WTF-8 paths" {
......@@ -2093,71 +2103,65 @@ test "invalid UTF-8/WTF-8 paths" {
20932103 // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
20942104 const invalid_path = try ctx.transformPath("\xFF");
20952105
2096 try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{}));
2106 try expectError(expected_err, ctx.dir.openFile(io, invalid_path, .{}));
20972107
2098 try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{}));
2108 try expectError(expected_err, ctx.dir.createFile(io, invalid_path, .{}));
20992109
2100 try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path));
2110 try expectError(expected_err, ctx.dir.createDir(io, invalid_path, .default_dir));
21012111
2102 try testing.expectError(expected_err, ctx.dir.makePath(invalid_path));
2103 try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{}));
2112 try expectError(expected_err, ctx.dir.createDirPath(io, invalid_path));
2113 try expectError(expected_err, ctx.dir.createDirPathOpen(io, invalid_path, .{}));
21042114
2105 try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{}));
2115 try expectError(expected_err, ctx.dir.openDir(io, invalid_path, .{}));
21062116
2107 try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path));
2117 try expectError(expected_err, ctx.dir.deleteFile(io, invalid_path));
21082118
2109 try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path));
2119 try expectError(expected_err, ctx.dir.deleteDir(io, invalid_path));
21102120
2111 try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path));
2121 try expectError(expected_err, ctx.dir.rename(invalid_path, ctx.dir, invalid_path, io));
21122122
2113 try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{}));
2114 if (native_os == .wasi) {
2115 try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));
2116 }
2123 try expectError(expected_err, ctx.dir.symLink(io, invalid_path, invalid_path, .{}));
21172124
2118 try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));
2119 if (native_os == .wasi) {
2120 try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));
2121 }
2125 try expectError(expected_err, ctx.dir.readLink(io, invalid_path, &[_]u8{}));
21222126
2123 try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{}));
2124 try testing.expectError(expected_err, ctx.dir.readFileAlloc(invalid_path, testing.allocator, .limited(0)));
2127 try expectError(expected_err, ctx.dir.readFile(io, invalid_path, &[_]u8{}));
2128 try expectError(expected_err, ctx.dir.readFileAlloc(io, invalid_path, testing.allocator, .limited(0)));
21252129
2126 try testing.expectError(expected_err, ctx.dir.deleteTree(invalid_path));
2127 try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(invalid_path));
2130 try expectError(expected_err, ctx.dir.deleteTree(io, invalid_path));
2131 try expectError(expected_err, ctx.dir.deleteTreeMinStackSize(io, invalid_path));
21282132
2129 try testing.expectError(expected_err, ctx.dir.writeFile(.{ .sub_path = invalid_path, .data = "" }));
2133 try expectError(expected_err, ctx.dir.writeFile(io, .{ .sub_path = invalid_path, .data = "" }));
21302134
2131 try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{}));
2135 try expectError(expected_err, ctx.dir.access(io, invalid_path, .{}));
21322136
2133 var dir = ctx.dir.adaptToNewApi();
2134 try testing.expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{}));
2135 try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));
2137 var dir = ctx.dir;
2138 try expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{}));
2139 try expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, io, .{}));
21362140
2137 try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));
2141 try expectError(expected_err, ctx.dir.statFile(io, invalid_path, .{}));
21382142
21392143 if (native_os != .wasi) {
2140 try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));
2141 try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));
2144 try expectError(expected_err, ctx.dir.realPathFile(io, invalid_path, &[_]u8{}));
2145 try expectError(expected_err, ctx.dir.realPathFileAlloc(io, invalid_path, testing.allocator));
21422146 }
21432147
2144 try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path));
2148 try expectError(expected_err, Dir.rename(ctx.dir, invalid_path, ctx.dir, invalid_path, io));
21452149
21462150 if (native_os != .wasi and ctx.path_type != .relative) {
2147 try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));
2148 try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));
2149 try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));
2150 try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));
2151 try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));
2152 try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));
2153 try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));
2154 try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));
2155 try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));
2156 try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));
2157 var readlink_buf: [fs.max_path_bytes]u8 = undefined;
2158 try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));
2159 try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));
2160 try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));
2151 var buf: [Dir.max_path_bytes]u8 = undefined;
2152 try expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, io, .{}));
2153 try expectError(expected_err, Dir.createDirAbsolute(io, invalid_path, .default_dir));
2154 try expectError(expected_err, Dir.deleteDirAbsolute(io, invalid_path));
2155 try expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path, io));
2156 try expectError(expected_err, Dir.openDirAbsolute(io, invalid_path, .{}));
2157 try expectError(expected_err, Dir.openFileAbsolute(io, invalid_path, .{}));
2158 try expectError(expected_err, Dir.accessAbsolute(io, invalid_path, .{}));
2159 try expectError(expected_err, Dir.createFileAbsolute(io, invalid_path, .{}));
2160 try expectError(expected_err, Dir.deleteFileAbsolute(io, invalid_path));
2161 try expectError(expected_err, Dir.readLinkAbsolute(io, invalid_path, &buf));
2162 try expectError(expected_err, Dir.symLinkAbsolute(io, invalid_path, invalid_path, .{}));
2163 try expectError(expected_err, Dir.realPathFileAbsolute(io, invalid_path, &buf));
2164 try expectError(expected_err, Dir.realPathFileAbsoluteAlloc(io, invalid_path, testing.allocator));
21612165 }
21622166 }
21632167 }.impl);
......@@ -2171,15 +2175,15 @@ test "read file non vectored" {
21712175
21722176 const contents = "hello, world!\n";
21732177
2174 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2175 defer file.close();
2178 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2179 defer file.close(io);
21762180 {
2177 var file_writer: std.fs.File.Writer = .init(file, &.{});
2181 var file_writer: File.Writer = .init(file, io, &.{});
21782182 try file_writer.interface.writeAll(contents);
21792183 try file_writer.interface.flush();
21802184 }
21812185
2182 var file_reader: std.Io.File.Reader = .initAdapted(file, io, &.{});
2186 var file_reader: std.Io.File.Reader = .init(file, io, &.{});
21832187
21842188 var write_buffer: [100]u8 = undefined;
21852189 var w: std.Io.Writer = .fixed(&write_buffer);
......@@ -2191,8 +2195,8 @@ test "read file non vectored" {
21912195 else => |e| return e,
21922196 };
21932197 }
2194 try testing.expectEqualStrings(contents, w.buffered());
2195 try testing.expectEqual(contents.len, i);
2198 try expectEqualStrings(contents, w.buffered());
2199 try expectEqual(contents.len, i);
21962200}
21972201
21982202test "seek keeping partial buffer" {
......@@ -2203,18 +2207,18 @@ test "seek keeping partial buffer" {
22032207
22042208 const contents = "0123456789";
22052209
2206 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });
2207 defer file.close();
2210 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2211 defer file.close(io);
22082212 {
2209 var file_writer: std.fs.File.Writer = .init(file, &.{});
2213 var file_writer: File.Writer = .init(file, io, &.{});
22102214 try file_writer.interface.writeAll(contents);
22112215 try file_writer.interface.flush();
22122216 }
22132217
22142218 var read_buffer: [3]u8 = undefined;
2215 var file_reader: Io.File.Reader = .initAdapted(file, io, &read_buffer);
2219 var file_reader: Io.File.Reader = .init(file, io, &read_buffer);
22162220
2217 try testing.expectEqual(0, file_reader.logicalPos());
2221 try expectEqual(0, file_reader.logicalPos());
22182222
22192223 var buf: [4]u8 = undefined;
22202224 try file_reader.interface.readSliceAll(&buf);
......@@ -2224,18 +2228,18 @@ test "seek keeping partial buffer" {
22242228 return;
22252229 }
22262230
2227 try testing.expectEqual(4, file_reader.logicalPos());
2228 try testing.expectEqual(7, file_reader.pos);
2231 try expectEqual(4, file_reader.logicalPos());
2232 try expectEqual(7, file_reader.pos);
22292233 try file_reader.seekTo(6);
2230 try testing.expectEqual(6, file_reader.logicalPos());
2231 try testing.expectEqual(7, file_reader.pos);
2234 try expectEqual(6, file_reader.logicalPos());
2235 try expectEqual(7, file_reader.pos);
22322236
2233 try testing.expectEqualStrings("0123", &buf);
2237 try expectEqualStrings("0123", &buf);
22342238
22352239 const n = try file_reader.interface.readSliceShort(&buf);
2236 try testing.expectEqual(4, n);
2240 try expectEqual(4, n);
22372241
2238 try testing.expectEqualStrings("6789", &buf);
2242 try expectEqualStrings("6789", &buf);
22392243}
22402244
22412245test "seekBy" {
......@@ -2244,16 +2248,16 @@ test "seekBy" {
22442248 var tmp_dir = testing.tmpDir(.{});
22452249 defer tmp_dir.cleanup();
22462250
2247 try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" });
2248 const f = try tmp_dir.dir.openFile("blah.txt", .{ .mode = .read_only });
2249 defer f.close();
2251 try tmp_dir.dir.writeFile(io, .{ .sub_path = "blah.txt", .data = "let's test seekBy" });
2252 const f = try tmp_dir.dir.openFile(io, "blah.txt", .{ .mode = .read_only });
2253 defer f.close(io);
22502254 var reader = f.readerStreaming(io, &.{});
22512255 try reader.seekBy(2);
22522256
22532257 var buffer: [20]u8 = undefined;
22542258 const n = try reader.interface.readSliceShort(&buffer);
2255 try testing.expectEqual(15, n);
2256 try testing.expectEqualStrings("t's test seekBy", buffer[0..15]);
2259 try expectEqual(15, n);
2260 try expectEqualStrings("t's test seekBy", buffer[0..15]);
22572261}
22582262
22592263test "seekTo flushes buffered data" {
......@@ -2264,11 +2268,11 @@ test "seekTo flushes buffered data" {
22642268
22652269 const contents = "data";
22662270
2267 const file = try tmp.dir.createFile("seek.bin", .{ .read = true });
2268 defer file.close();
2271 const file = try tmp.dir.createFile(io, "seek.bin", .{ .read = true });
2272 defer file.close(io);
22692273 {
22702274 var buf: [16]u8 = undefined;
2271 var file_writer = std.fs.File.writer(file, &buf);
2275 var file_writer = file.writer(io, &buf);
22722276
22732277 try file_writer.interface.writeAll(contents);
22742278 try file_writer.seekTo(8);
......@@ -2276,11 +2280,11 @@ test "seekTo flushes buffered data" {
22762280 }
22772281
22782282 var read_buffer: [16]u8 = undefined;
2279 var file_reader: std.Io.File.Reader = .initAdapted(file, io, &read_buffer);
2283 var file_reader: std.Io.File.Reader = .init(file, io, &read_buffer);
22802284
22812285 var buf: [4]u8 = undefined;
22822286 try file_reader.interface.readSliceAll(&buf);
2283 try std.testing.expectEqualStrings(contents, &buf);
2287 try expectEqualStrings(contents, &buf);
22842288}
22852289
22862290test "File.Writer sendfile with buffered contents" {
......@@ -2290,11 +2294,11 @@ test "File.Writer sendfile with buffered contents" {
22902294 defer tmp_dir.cleanup();
22912295
22922296 {
2293 try tmp_dir.dir.writeFile(.{ .sub_path = "a", .data = "bcd" });
2294 const in = try tmp_dir.dir.openFile("a", .{});
2295 defer in.close();
2296 const out = try tmp_dir.dir.createFile("b", .{});
2297 defer out.close();
2297 try tmp_dir.dir.writeFile(io, .{ .sub_path = "a", .data = "bcd" });
2298 const in = try tmp_dir.dir.openFile(io, "a", .{});
2299 defer in.close(io);
2300 const out = try tmp_dir.dir.createFile(io, "b", .{});
2301 defer out.close(io);
22982302
22992303 var in_buf: [2]u8 = undefined;
23002304 var in_r = in.reader(io, &in_buf);
......@@ -2302,16 +2306,332 @@ test "File.Writer sendfile with buffered contents" {
23022306 try in_r.interface.fill(2);
23032307
23042308 var out_buf: [1]u8 = undefined;
2305 var out_w = out.writerStreaming(&out_buf);
2309 var out_w = out.writerStreaming(io, &out_buf);
23062310 try out_w.interface.writeByte('a');
2307 try testing.expectEqual(3, try out_w.interface.sendFileAll(&in_r, .unlimited));
2311 try expectEqual(3, try out_w.interface.sendFileAll(&in_r, .unlimited));
23082312 try out_w.interface.flush();
23092313 }
23102314
2311 var check = try tmp_dir.dir.openFile("b", .{});
2312 defer check.close();
2315 var check = try tmp_dir.dir.openFile(io, "b", .{});
2316 defer check.close(io);
23132317 var check_buf: [4]u8 = undefined;
23142318 var check_r = check.reader(io, &check_buf);
2315 try testing.expectEqualStrings("abcd", try check_r.interface.take(4));
2316 try testing.expectError(error.EndOfStream, check_r.interface.takeByte());
2319 try expectEqualStrings("abcd", try check_r.interface.take(4));
2320 try expectError(error.EndOfStream, check_r.interface.takeByte());
2321}
2322
2323test "readlink on Windows" {
2324 if (native_os != .windows) return error.SkipZigTest;
2325
2326 const io = testing.io;
2327
2328 try testReadLinkWindows(io, "C:\\ProgramData", "C:\\Users\\All Users");
2329 try testReadLinkWindows(io, "C:\\Users\\Default", "C:\\Users\\Default User");
2330 try testReadLinkWindows(io, "C:\\Users", "C:\\Documents and Settings");
2331}
2332
2333fn testReadLinkWindows(io: Io, target_path: []const u8, symlink_path: []const u8) !void {
2334 var buffer: [Dir.max_path_bytes]u8 = undefined;
2335 const len = try Dir.readLinkAbsolute(io, symlink_path, &buffer);
2336 const given = buffer[0..len];
2337 try expect(mem.eql(u8, target_path, given));
2338}
2339
2340test "readlinkat" {
2341 const io = testing.io;
2342
2343 var tmp = tmpDir(.{});
2344 defer tmp.cleanup();
2345
2346 // create file
2347 try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = "nonsense" });
2348
2349 // create a symbolic link
2350 tmp.dir.symLink(io, "file.txt", "link", .{}) catch |err| switch (err) {
2351 error.AccessDenied => {
2352 // Symlink requires admin privileges on windows, so this test can legitimately fail.
2353 if (native_os == .windows) return error.SkipZigTest;
2354 },
2355 else => |e| return e,
2356 };
2357
2358 // read the link
2359 var buffer: [Dir.max_path_bytes]u8 = undefined;
2360 const read_link = buffer[0..try tmp.dir.readLink(io, "link", &buffer)];
2361 try expectEqualStrings("file.txt", read_link);
2362}
2363
2364test "fchmodat smoke test" {
2365 if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest;
2366
2367 const io = testing.io;
2368
2369 var tmp = tmpDir(.{});
2370 defer tmp.cleanup();
2371
2372 try expectError(error.FileNotFound, tmp.dir.setFilePermissions(io, "regfile", .fromMode(0o666), .{}));
2373 const file = try tmp.dir.createFile(io, "regfile", .{
2374 .exclusive = true,
2375 .permissions = .fromMode(0o644),
2376 });
2377 file.close(io);
2378
2379 if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and
2380 builtin.os.tag == .linux and !builtin.link_libc)
2381 {
2382 return error.SkipZigTest; // No `fstatat()`.
2383 }
2384
2385 try tmp.dir.symLink(io, "regfile", "symlink", .{});
2386 const sym_mode = blk: {
2387 const st = try tmp.dir.statFile(io, "symlink", .{ .follow_symlinks = false });
2388 break :blk st.permissions.toMode() & 0b111_111_111;
2389 };
2390
2391 try tmp.dir.setFilePermissions(io, "regfile", .fromMode(0o640), .{});
2392 try expectMode(io, tmp.dir, "regfile", .fromMode(0o640));
2393 try tmp.dir.setFilePermissions(io, "regfile", .fromMode(0o600), .{ .follow_symlinks = false });
2394 try expectMode(io, tmp.dir, "regfile", .fromMode(0o600));
2395
2396 try tmp.dir.setFilePermissions(io, "symlink", .fromMode(0o640), .{});
2397 try expectMode(io, tmp.dir, "regfile", .fromMode(0o640));
2398 try expectMode(io, tmp.dir, "symlink", .fromMode(sym_mode));
2399
2400 var test_link = true;
2401 tmp.dir.setFilePermissions(io, "symlink", .fromMode(0o600), .{ .follow_symlinks = false }) catch |err| switch (err) {
2402 error.OperationUnsupported => test_link = false,
2403 else => |e| return e,
2404 };
2405 if (test_link) try expectMode(io, tmp.dir, "symlink", .fromMode(0o600));
2406 try expectMode(io, tmp.dir, "regfile", .fromMode(0o640));
2407}
2408
2409fn expectMode(io: Io, dir: Dir, file: []const u8, permissions: File.Permissions) !void {
2410 const mode = permissions.toMode();
2411 const st = try dir.statFile(io, file, .{ .follow_symlinks = false });
2412 const found_mode = st.permissions.toMode();
2413 try expectEqual(mode, found_mode & 0b111_111_111);
2414}
2415
2416test "isatty" {
2417 const io = testing.io;
2418
2419 var tmp = tmpDir(.{});
2420 defer tmp.cleanup();
2421
2422 var file = try tmp.dir.createFile(io, "foo", .{});
2423 defer file.close(io);
2424
2425 try expectEqual(false, try file.isTty(io));
2426}
2427
2428test "read positional empty buffer" {
2429 const io = testing.io;
2430
2431 var tmp = tmpDir(.{});
2432 defer tmp.cleanup();
2433
2434 var file = try tmp.dir.createFile(io, "pread_empty", .{ .read = true });
2435 defer file.close(io);
2436
2437 var buffer: [0]u8 = undefined;
2438 try expectEqual(0, try file.readPositional(io, &.{&buffer}, 0));
2439}
2440
2441test "write streaming empty buffer" {
2442 const io = testing.io;
2443
2444 var tmp = tmpDir(.{});
2445 defer tmp.cleanup();
2446
2447 var file = try tmp.dir.createFile(io, "write_empty", .{});
2448 defer file.close(io);
2449
2450 const buffer: [0]u8 = .{};
2451 try file.writeStreamingAll(io, &buffer);
2452}
2453
2454test "write positional empty buffer" {
2455 const io = testing.io;
2456
2457 var tmp = tmpDir(.{});
2458 defer tmp.cleanup();
2459
2460 var file = try tmp.dir.createFile(io, "pwrite_empty", .{});
2461 defer file.close(io);
2462
2463 const buffer: [0]u8 = .{};
2464 try expectEqual(0, try file.writePositional(io, &.{&buffer}, 0));
2465}
2466
2467test "access smoke test" {
2468 if (native_os == .wasi) return error.SkipZigTest;
2469 if (native_os == .windows) return error.SkipZigTest;
2470 if (native_os == .openbsd) return error.SkipZigTest;
2471
2472 const io = testing.io;
2473
2474 var tmp = tmpDir(.{});
2475 defer tmp.cleanup();
2476
2477 {
2478 // Create some file using `open`.
2479 const file = try tmp.dir.createFile(io, "some_file", .{ .read = true, .exclusive = true });
2480 file.close(io);
2481 }
2482
2483 {
2484 // Try to access() the file
2485 if (native_os == .windows) {
2486 try tmp.dir.access(io, "some_file", .{});
2487 } else {
2488 try tmp.dir.access(io, "some_file", .{ .read = true, .write = true });
2489 }
2490 }
2491
2492 {
2493 // Try to access() a non-existent file - should fail with error.FileNotFound
2494 try expectError(error.FileNotFound, tmp.dir.access(io, "some_other_file", .{}));
2495 }
2496
2497 {
2498 // Create some directory
2499 try tmp.dir.createDir(io, "some_dir", .default_dir);
2500 }
2501
2502 {
2503 // Try to access() the directory
2504 try tmp.dir.access(io, "some_dir", .{});
2505 }
2506}
2507
2508test "write streaming a long vector" {
2509 const io = testing.io;
2510
2511 var tmp = tmpDir(.{});
2512 defer tmp.cleanup();
2513
2514 var file = try tmp.dir.createFile(io, "pwritev", .{});
2515 defer file.close(io);
2516
2517 var vecs: [2000][]const u8 = undefined;
2518 for (&vecs) |*v| v.* = "a";
2519
2520 const n = try file.writePositional(io, &vecs, 0);
2521 try expect(n <= vecs.len);
2522}
2523
2524test "open smoke test" {
2525 if (native_os == .wasi) return error.SkipZigTest;
2526 if (native_os == .windows) return error.SkipZigTest;
2527 if (native_os == .openbsd) return error.SkipZigTest;
2528
2529 // TODO verify file attributes using `fstat`
2530
2531 var tmp = tmpDir(.{});
2532 defer tmp.cleanup();
2533
2534 const io = testing.io;
2535
2536 {
2537 // Create some file using `open`.
2538 const file = try tmp.dir.createFile(io, "some_file", .{ .exclusive = true });
2539 file.close(io);
2540 }
2541
2542 // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
2543 try expectError(
2544 error.PathAlreadyExists,
2545 tmp.dir.createFile(io, "some_file", .{ .exclusive = true }),
2546 );
2547
2548 {
2549 // Try opening without exclusive flag.
2550 const file = try tmp.dir.createFile(io, "some_file", .{});
2551 file.close(io);
2552 }
2553
2554 try expectError(error.NotDir, tmp.dir.openDir(io, "some_file", .{}));
2555 try tmp.dir.createDir(io, "some_dir", .default_dir);
2556
2557 {
2558 const dir = try tmp.dir.openDir(io, "some_dir", .{});
2559 dir.close(io);
2560 }
2561
2562 // Try opening as file which should fail.
2563 try expectError(error.IsDir, tmp.dir.openFile(io, "some_dir", .{ .allow_directory = false }));
2564}
2565
2566test "hard link with different directories" {
2567 if (native_os == .wasi or native_os == .windows) return error.SkipZigTest;
2568
2569 const io = testing.io;
2570
2571 var tmp = tmpDir(.{});
2572 defer tmp.cleanup();
2573
2574 const target_name = "link-target";
2575 const link_name = "newlink";
2576
2577 const subdir = try tmp.dir.createDirPathOpen(io, "subdir", .{});
2578
2579 defer tmp.dir.deleteFile(io, target_name) catch {};
2580 try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" });
2581
2582 // Test 1: link from file in subdir back up to target in parent directory
2583 tmp.dir.hardLink(target_name, subdir, link_name, io, .{}) catch |err| switch (err) {
2584 error.OperationUnsupported => return error.SkipZigTest,
2585 else => |e| return e,
2586 };
2587
2588 const efd = try tmp.dir.openFile(io, target_name, .{});
2589 defer efd.close(io);
2590
2591 const nfd = try subdir.openFile(io, link_name, .{});
2592 defer nfd.close(io);
2593
2594 {
2595 const e_stat = try efd.stat(io);
2596 const n_stat = try nfd.stat(io);
2597
2598 try expectEqual(e_stat.inode, n_stat.inode);
2599 try expectEqual(2, e_stat.nlink);
2600 try expectEqual(2, n_stat.nlink);
2601 }
2602
2603 // Test 2: remove link
2604 try subdir.deleteFile(io, link_name);
2605 const e_stat = try efd.stat(io);
2606 try expectEqual(1, e_stat.nlink);
2607}
2608
2609test "stat smoke test" {
2610 if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest;
2611
2612 const io = testing.io;
2613
2614 var tmp = tmpDir(.{});
2615 defer tmp.cleanup();
2616
2617 // create dummy file
2618 const contents = "nonsense";
2619 try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = contents });
2620
2621 // fetch file's info on the opened fd directly
2622 const file = try tmp.dir.openFile(io, "file.txt", .{});
2623 const stat = try file.stat(io);
2624 defer file.close(io);
2625
2626 // now repeat but using directory handle instead
2627 const statat = try tmp.dir.statFile(io, "file.txt", .{ .follow_symlinks = false });
2628
2629 try expectEqual(stat.inode, statat.inode);
2630 try expectEqual(stat.nlink, statat.nlink);
2631 try expectEqual(stat.size, statat.size);
2632 try expectEqual(stat.permissions, statat.permissions);
2633 try expectEqual(stat.kind, statat.kind);
2634 try expectEqual(stat.atime, statat.atime);
2635 try expectEqual(stat.mtime, statat.mtime);
2636 try expectEqual(stat.ctime, statat.ctime);
23172637}
lib/std/hash/benchmark.zig+3-2
......@@ -1,7 +1,8 @@
11// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
2const builtin = @import("builtin");
23
34const std = @import("std");
4const builtin = @import("builtin");
5const Io = std.Io;
56const time = std.time;
67const Timer = time.Timer;
78const hash = std.hash;
......@@ -354,7 +355,7 @@ fn mode(comptime x: comptime_int) comptime_int {
354355
355356pub fn main() !void {
356357 var stdout_buffer: [0x100]u8 = undefined;
357 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
358 var stdout_writer = Io.File.stdout().writer(&stdout_buffer);
358359 const stdout = &stdout_writer.interface;
359360
360361 var buffer: [1024]u8 = undefined;
lib/std/heap/debug_allocator.zig+26-29
......@@ -84,7 +84,7 @@ const builtin = @import("builtin");
8484const StackTrace = std.builtin.StackTrace;
8585
8686const std = @import("std");
87const log = std.log.scoped(.gpa);
87const log = std.log.scoped(.DebugAllocator);
8888const math = std.math;
8989const assert = std.debug.assert;
9090const mem = std.mem;
......@@ -425,7 +425,6 @@ pub fn DebugAllocator(comptime config: Config) type {
425425 bucket: *BucketHeader,
426426 size_class_index: usize,
427427 used_bits_count: usize,
428 tty_config: std.Io.tty.Config,
429428 ) usize {
430429 const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index));
431430 const slot_count = slot_counts[size_class_index];
......@@ -445,7 +444,7 @@ pub fn DebugAllocator(comptime config: Config) type {
445444 addr,
446445 std.debug.FormatStackTrace{
447446 .stack_trace = stack_trace,
448 .tty_config = tty_config,
447 .terminal_mode = std.log.terminalMode(),
449448 },
450449 });
451450 leaks += 1;
......@@ -460,14 +459,12 @@ pub fn DebugAllocator(comptime config: Config) type {
460459 pub fn detectLeaks(self: *Self) usize {
461460 var leaks: usize = 0;
462461
463 const tty_config: std.Io.tty.Config = .detect(.stderr());
464
465462 for (self.buckets, 0..) |init_optional_bucket, size_class_index| {
466463 var optional_bucket = init_optional_bucket;
467464 const slot_count = slot_counts[size_class_index];
468465 const used_bits_count = usedBitsCount(slot_count);
469466 while (optional_bucket) |bucket| {
470 leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count, tty_config);
467 leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count);
471468 optional_bucket = bucket.prev;
472469 }
473470 }
......@@ -480,7 +477,7 @@ pub fn DebugAllocator(comptime config: Config) type {
480477 @intFromPtr(large_alloc.bytes.ptr),
481478 std.debug.FormatStackTrace{
482479 .stack_trace = stack_trace,
483 .tty_config = tty_config,
480 .terminal_mode = std.log.terminalMode(),
484481 },
485482 });
486483 leaks += 1;
......@@ -534,21 +531,21 @@ pub fn DebugAllocator(comptime config: Config) type {
534531 }
535532
536533 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {
534 @branchHint(.cold);
537535 var addr_buf: [stack_n]usize = undefined;
538536 const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
539 const tty_config: std.Io.tty.Config = .detect(.stderr());
540537 log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{
541538 std.debug.FormatStackTrace{
542539 .stack_trace = alloc_stack_trace,
543 .tty_config = tty_config,
540 .terminal_mode = std.log.terminalMode(),
544541 },
545542 std.debug.FormatStackTrace{
546543 .stack_trace = free_stack_trace,
547 .tty_config = tty_config,
544 .terminal_mode = std.log.terminalMode(),
548545 },
549546 std.debug.FormatStackTrace{
550547 .stack_trace = second_free_stack_trace,
551 .tty_config = tty_config,
548 .terminal_mode = std.log.terminalMode(),
552549 },
553550 });
554551 }
......@@ -588,19 +585,19 @@ pub fn DebugAllocator(comptime config: Config) type {
588585 }
589586
590587 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
588 @branchHint(.cold);
591589 var addr_buf: [stack_n]usize = undefined;
592590 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
593 const tty_config: std.Io.tty.Config = .detect(.stderr());
594591 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
595592 entry.value_ptr.bytes.len,
596593 old_mem.len,
597594 std.debug.FormatStackTrace{
598595 .stack_trace = entry.value_ptr.getStackTrace(.alloc),
599 .tty_config = tty_config,
596 .terminal_mode = std.log.terminalMode(),
600597 },
601598 std.debug.FormatStackTrace{
602599 .stack_trace = free_stack_trace,
603 .tty_config = tty_config,
600 .terminal_mode = std.log.terminalMode(),
604601 },
605602 });
606603 }
......@@ -701,19 +698,19 @@ pub fn DebugAllocator(comptime config: Config) type {
701698 }
702699
703700 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
701 @branchHint(.cold);
704702 var addr_buf: [stack_n]usize = undefined;
705703 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
706 const tty_config: std.Io.tty.Config = .detect(.stderr());
707704 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
708705 entry.value_ptr.bytes.len,
709706 old_mem.len,
710707 std.debug.FormatStackTrace{
711708 .stack_trace = entry.value_ptr.getStackTrace(.alloc),
712 .tty_config = tty_config,
709 .terminal_mode = std.log.terminalMode(),
713710 },
714711 std.debug.FormatStackTrace{
715712 .stack_trace = free_stack_trace,
716 .tty_config = tty_config,
713 .terminal_mode = std.log.terminalMode(),
717714 },
718715 });
719716 }
......@@ -935,32 +932,32 @@ pub fn DebugAllocator(comptime config: Config) type {
935932 var addr_buf: [stack_n]usize = undefined;
936933 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
937934 if (old_memory.len != requested_size) {
938 const tty_config: std.Io.tty.Config = .detect(.stderr());
935 @branchHint(.cold);
939936 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
940937 requested_size,
941938 old_memory.len,
942939 std.debug.FormatStackTrace{
943940 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
944 .tty_config = tty_config,
941 .terminal_mode = std.log.terminalMode(),
945942 },
946943 std.debug.FormatStackTrace{
947944 .stack_trace = free_stack_trace,
948 .tty_config = tty_config,
945 .terminal_mode = std.log.terminalMode(),
949946 },
950947 });
951948 }
952949 if (alignment != slot_alignment) {
953 const tty_config: std.Io.tty.Config = .detect(.stderr());
950 @branchHint(.cold);
954951 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
955952 slot_alignment.toByteUnits(),
956953 alignment.toByteUnits(),
957954 std.debug.FormatStackTrace{
958955 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
959 .tty_config = tty_config,
956 .terminal_mode = std.log.terminalMode(),
960957 },
961958 std.debug.FormatStackTrace{
962959 .stack_trace = free_stack_trace,
963 .tty_config = tty_config,
960 .terminal_mode = std.log.terminalMode(),
964961 },
965962 });
966963 }
......@@ -1044,32 +1041,32 @@ pub fn DebugAllocator(comptime config: Config) type {
10441041 var addr_buf: [stack_n]usize = undefined;
10451042 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
10461043 if (memory.len != requested_size) {
1047 const tty_config: std.Io.tty.Config = .detect(.stderr());
1044 @branchHint(.cold);
10481045 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
10491046 requested_size,
10501047 memory.len,
10511048 std.debug.FormatStackTrace{
10521049 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
1053 .tty_config = tty_config,
1050 .terminal_mode = std.log.terminalMode(),
10541051 },
10551052 std.debug.FormatStackTrace{
10561053 .stack_trace = free_stack_trace,
1057 .tty_config = tty_config,
1054 .terminal_mode = std.log.terminalMode(),
10581055 },
10591056 });
10601057 }
10611058 if (alignment != slot_alignment) {
1062 const tty_config: std.Io.tty.Config = .detect(.stderr());
1059 @branchHint(.cold);
10631060 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
10641061 slot_alignment.toByteUnits(),
10651062 alignment.toByteUnits(),
10661063 std.debug.FormatStackTrace{
10671064 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
1068 .tty_config = tty_config,
1065 .terminal_mode = std.log.terminalMode(),
10691066 },
10701067 std.debug.FormatStackTrace{
10711068 .stack_trace = free_stack_trace,
1072 .tty_config = tty_config,
1069 .terminal_mode = std.log.terminalMode(),
10731070 },
10741071 });
10751072 }
lib/std/http.zig+1-1
......@@ -2,7 +2,7 @@ const builtin = @import("builtin");
22const std = @import("std.zig");
33const assert = std.debug.assert;
44const Writer = std.Io.Writer;
5const File = std.fs.File;
5const File = std.Io.File;
66
77pub const Client = @import("http/Client.zig");
88pub const Server = @import("http/Server.zig");
lib/std/http/Client.zig+3-1
......@@ -1473,6 +1473,8 @@ pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{N
14731473///
14741474/// This function is threadsafe.
14751475pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connection {
1476 const io = client.io;
1477
14761478 if (client.connection_pool.findConnection(.{
14771479 .host = path,
14781480 .port = 0,
......@@ -1485,7 +1487,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
14851487 conn.* = .{ .data = undefined };
14861488
14871489 const stream = try Io.net.connectUnixSocket(path);
1488 errdefer stream.close();
1490 errdefer stream.close(io);
14891491
14901492 conn.data = .{
14911493 .stream = stream,
lib/std/json/dynamic.zig+3-4
......@@ -47,10 +47,9 @@ pub const Value = union(enum) {
4747 }
4848
4949 pub fn dump(v: Value) void {
50 const w, _ = std.debug.lockStderrWriter(&.{});
51 defer std.debug.unlockStderrWriter();
52
53 json.Stringify.value(v, .{}, w) catch return;
50 const stderr = std.debug.lockStderr(&.{}, null);
51 defer std.debug.unlockStderr();
52 json.Stringify.value(v, .{}, &stderr.file_writer.interface) catch return;
5453 }
5554
5655 pub fn jsonStringify(value: @This(), jws: anytype) !void {
lib/std/log.zig+31-15
......@@ -15,7 +15,7 @@
1515//!
1616//! For an example implementation of the `logFn` function, see `defaultLog`,
1717//! which is the default implementation. It outputs to stderr, using color if
18//! the detected `std.Io.tty.Config` supports it. Its output looks like this:
18//! supported. Its output looks like this:
1919//! ```
2020//! error: this is an error
2121//! error(scope): this is an error with a non-default scope
......@@ -80,6 +80,14 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool {
8080 return @intFromEnum(level) <= @intFromEnum(std.options.log_level);
8181}
8282
83pub const terminalMode = std.options.logTerminalMode;
84
85pub fn defaultTerminalMode() std.Io.Terminal.Mode {
86 const stderr = std.debug.lockStderr(&.{}).terminal();
87 std.debug.unlockStderr();
88 return stderr.mode;
89}
90
8391/// The default implementation for the log function. Custom log functions may
8492/// forward log messages to this function.
8593///
......@@ -92,25 +100,33 @@ pub fn defaultLog(
92100 args: anytype,
93101) void {
94102 var buffer: [64]u8 = undefined;
95 const stderr, const ttyconf = std.debug.lockStderrWriter(&buffer);
96 defer std.debug.unlockStderrWriter();
97 ttyconf.setColor(stderr, switch (level) {
103 const stderr = std.debug.lockStderr(&buffer).terminal();
104 defer std.debug.unlockStderr();
105 return defaultLogFileTerminal(level, scope, format, args, stderr) catch {};
106}
107
108pub fn defaultLogFileTerminal(
109 comptime level: Level,
110 comptime scope: @EnumLiteral(),
111 comptime format: []const u8,
112 args: anytype,
113 t: std.Io.Terminal,
114) std.Io.Writer.Error!void {
115 t.setColor(switch (level) {
98116 .err => .red,
99117 .warn => .yellow,
100118 .info => .green,
101119 .debug => .magenta,
102120 }) catch {};
103 ttyconf.setColor(stderr, .bold) catch {};
104 stderr.writeAll(level.asText()) catch return;
105 ttyconf.setColor(stderr, .reset) catch {};
106 ttyconf.setColor(stderr, .dim) catch {};
107 ttyconf.setColor(stderr, .bold) catch {};
108 if (scope != .default) {
109 stderr.print("({s})", .{@tagName(scope)}) catch return;
110 }
111 stderr.writeAll(": ") catch return;
112 ttyconf.setColor(stderr, .reset) catch {};
113 stderr.print(format ++ "\n", args) catch return;
121 t.setColor(.bold) catch {};
122 try t.writer.writeAll(level.asText());
123 t.setColor(.reset) catch {};
124 t.setColor(.dim) catch {};
125 t.setColor(.bold) catch {};
126 if (scope != .default) try t.writer.print("({t})", .{scope});
127 try t.writer.writeAll(": ");
128 t.setColor(.reset) catch {};
129 try t.writer.print(format ++ "\n", args);
114130}
115131
116132/// Returns a scoped logging namespace that logs all messages using the scope
lib/std/os.zig+5-131
......@@ -21,7 +21,6 @@ const mem = std.mem;
2121const elf = std.elf;
2222const fs = std.fs;
2323const dl = @import("dynamic_library.zig");
24const max_path_bytes = std.fs.max_path_bytes;
2524const posix = std.posix;
2625const native_os = builtin.os.tag;
2726
......@@ -31,7 +30,6 @@ pub const uefi = @import("os/uefi.zig");
3130pub const wasi = @import("os/wasi.zig");
3231pub const emscripten = @import("os/emscripten.zig");
3332pub const windows = @import("os/windows.zig");
34pub const freebsd = @import("os/freebsd.zig");
3533
3634test {
3735 _ = linux;
......@@ -56,135 +54,6 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o
5654 else => undefined,
5755};
5856
59pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
60 return switch (os.tag) {
61 .windows,
62 .driverkit,
63 .ios,
64 .maccatalyst,
65 .macos,
66 .tvos,
67 .visionos,
68 .watchos,
69 .linux,
70 .illumos,
71 .freebsd,
72 .serenity,
73 => true,
74
75 .dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
76 .netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
77 else => false,
78 };
79}
80
81/// Return canonical path of handle `fd`.
82///
83/// This function is very host-specific and is not universally supported by all hosts.
84/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
85/// unsupported on WASI.
86///
87/// * On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
88/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding.
89///
90/// Calling this function is usually a bug.
91pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.RealPathError![]u8 {
92 if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
93 @compileError("querying for canonical path of a handle is unsupported on this host");
94 }
95 switch (native_os) {
96 .windows => {
97 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
98 const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
99
100 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
101 return out_buffer[0..end_index];
102 },
103 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
104 // On macOS, we can use F.GETPATH fcntl command to query the OS for
105 // the path to the file descriptor.
106 @memset(out_buffer[0..max_path_bytes], 0);
107 switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) {
108 .SUCCESS => {},
109 .BADF => return error.FileNotFound,
110 .NOSPC => return error.NameTooLong,
111 .NOENT => return error.FileNotFound,
112 // TODO man pages for fcntl on macOS don't really tell you what
113 // errno values to expect when command is F.GETPATH...
114 else => |err| return posix.unexpectedErrno(err),
115 }
116 const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
117 return out_buffer[0..len];
118 },
119 .linux, .serenity => {
120 var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
121 const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/fd/{d}", .{fd}, 0) catch unreachable;
122
123 const target = posix.readlinkZ(proc_path, out_buffer) catch |err| {
124 switch (err) {
125 error.NotLink => unreachable,
126 error.BadPathName => unreachable,
127 error.UnsupportedReparsePointType => unreachable, // Windows-only
128 error.NetworkNotFound => unreachable, // Windows-only
129 else => |e| return e,
130 }
131 };
132 return target;
133 },
134 .illumos => {
135 var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
136 const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/path/{d}", .{fd}, 0) catch unreachable;
137
138 const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) {
139 error.UnsupportedReparsePointType => unreachable,
140 error.NotLink => unreachable,
141 else => |e| return e,
142 };
143 return target;
144 },
145 .freebsd => {
146 var kfile: std.c.kinfo_file = undefined;
147 kfile.structsize = std.c.KINFO_FILE_SIZE;
148 switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) {
149 .SUCCESS => {},
150 .BADF => return error.FileNotFound,
151 else => |err| return posix.unexpectedErrno(err),
152 }
153 const len = mem.findScalar(u8, &kfile.path, 0) orelse max_path_bytes;
154 if (len == 0) return error.NameTooLong;
155 const result = out_buffer[0..len];
156 @memcpy(result, kfile.path[0..len]);
157 return result;
158 },
159 .dragonfly => {
160 @memset(out_buffer[0..max_path_bytes], 0);
161 switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
162 .SUCCESS => {},
163 .BADF => return error.FileNotFound,
164 .RANGE => return error.NameTooLong,
165 else => |err| return posix.unexpectedErrno(err),
166 }
167 const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
168 return out_buffer[0..len];
169 },
170 .netbsd => {
171 @memset(out_buffer[0..max_path_bytes], 0);
172 switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
173 .SUCCESS => {},
174 .ACCES => return error.AccessDenied,
175 .BADF => return error.FileNotFound,
176 .NOENT => return error.FileNotFound,
177 .NOMEM => return error.SystemResources,
178 .RANGE => return error.NameTooLong,
179 else => |err| return posix.unexpectedErrno(err),
180 }
181 const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
182 return out_buffer[0..len];
183 },
184 else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
185 }
186}
187
18857pub const FstatError = error{
18958 SystemResources,
19059 AccessDenied,
......@@ -203,3 +72,8 @@ pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t {
20372 else => |err| return posix.unexpectedErrno(err),
20473 }
20574}
75
76pub fn defaultWasiCwd() std.os.wasi.fd_t {
77 // Expect the first preopen to be current working directory.
78 return 3;
79}
lib/std/os/freebsd.zig deleted-50
......@@ -1,50 +0,0 @@
1const std = @import("../std.zig");
2const fd_t = std.c.fd_t;
3const off_t = std.c.off_t;
4const unexpectedErrno = std.posix.unexpectedErrno;
5const errno = std.posix.errno;
6const builtin = @import("builtin");
7
8pub const CopyFileRangeError = std.posix.UnexpectedError || error{
9 /// If infd is not open for reading or outfd is not open for writing, or
10 /// opened for writing with O_APPEND, or if infd and outfd refer to the
11 /// same file.
12 BadFileFlags,
13 /// If the copy exceeds the process's file size limit or the maximum
14 /// file size for the file system outfd re- sides on.
15 FileTooBig,
16 /// A signal interrupted the system call before it could be completed.
17 /// This may happen for files on some NFS mounts. When this happens,
18 /// the values pointed to by inoffp and outoffp are reset to the
19 /// initial values for the system call.
20 Interrupted,
21 /// One of:
22 /// * infd and outfd refer to the same file and the byte ranges overlap.
23 /// * The flags argument is not zero.
24 /// * Either infd or outfd refers to a file object that is not a regular file.
25 InvalidArguments,
26 /// An I/O error occurred while reading/writing the files.
27 InputOutput,
28 /// Corrupted data was detected while reading from a file system.
29 CorruptedData,
30 /// Either infd or outfd refers to a directory.
31 IsDir,
32 /// File system that stores outfd is full.
33 NoSpaceLeft,
34};
35
36pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64, len: usize, flags: u32) CopyFileRangeError!usize {
37 const rc = std.c.copy_file_range(fd_in, off_in, fd_out, off_out, len, flags);
38 switch (errno(rc)) {
39 .SUCCESS => return @intCast(rc),
40 .BADF => return error.BadFileFlags,
41 .FBIG => return error.FileTooBig,
42 .INTR => return error.Interrupted,
43 .INVAL => return error.InvalidArguments,
44 .IO => return error.InputOutput,
45 .INTEGRITY => return error.CorruptedData,
46 .ISDIR => return error.IsDir,
47 .NOSPC => return error.NoSpaceLeft,
48 else => |err| return unexpectedErrno(err),
49 }
50}
lib/std/os/linux.zig+8-172
......@@ -1420,7 +1420,7 @@ pub fn chmod(path: [*:0]const u8, mode: mode_t) usize {
14201420 if (@hasField(SYS, "chmod")) {
14211421 return syscall2(.chmod, @intFromPtr(path), mode);
14221422 } else {
1423 return fchmodat(AT.FDCWD, path, mode, 0);
1423 return fchmodat(AT.FDCWD, path, mode);
14241424 }
14251425}
14261426
......@@ -1432,7 +1432,7 @@ pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize {
14321432 }
14331433}
14341434
1435pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t, _: u32) usize {
1435pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t) usize {
14361436 return syscall3(.fchmodat, @bitCast(@as(isize, fd)), @intFromPtr(path), mode);
14371437}
14381438
......@@ -1561,14 +1561,14 @@ pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize {
15611561 }
15621562}
15631563
1564pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: i32) usize {
1564pub fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: u32) usize {
15651565 return syscall5(
15661566 .linkat,
15671567 @as(usize, @bitCast(@as(isize, oldfd))),
15681568 @intFromPtr(oldpath),
15691569 @as(usize, @bitCast(@as(isize, newfd))),
15701570 @intFromPtr(newpath),
1571 @as(usize, @bitCast(@as(isize, flags))),
1571 flags,
15721572 );
15731573}
15741574
......@@ -6040,7 +6040,7 @@ pub const dirent64 = extern struct {
60406040 off: u64,
60416041 reclen: u16,
60426042 type: u8,
6043 name: u8, // field address is the address of first byte of name https://github.com/ziglang/zig/issues/173
6043 name: [0]u8,
60446044};
60456045
60466046pub const dl_phdr_info = extern struct {
......@@ -6891,10 +6891,6 @@ pub const utsname = extern struct {
68916891};
68926892pub const HOST_NAME_MAX = 64;
68936893
6894/// Flags used to request specific members in `Statx` be filled out.
6895/// The `Statx.mask` member will be updated with what information the kernel
6896/// returned. Callers must check this field since support varies by kernel
6897/// version and filesystem.
68986894pub const STATX = packed struct(u32) {
68996895 /// Want `mode & S.IFMT`.
69006896 TYPE: bool = false,
......@@ -6982,7 +6978,9 @@ pub const statx_timestamp = extern struct {
69826978
69836979/// Renamed to `Statx` to not conflict with the `statx` function.
69846980pub const Statx = extern struct {
6985 /// Mask of bits indicating filled fields.
6981 /// Mask of bits indicating filled fields. Updated with what information
6982 /// the kernel returned. Callers must check this field since support varies
6983 /// by kernel version and filesystem.
69866984 mask: STATX,
69876985 /// Block size for filesystem I/O.
69886986 blksize: u32,
......@@ -9872,165 +9870,3 @@ pub const cmsghdr = extern struct {
98729870 level: i32,
98739871 type: i32,
98749872};
9875
9876/// The syscalls, but with Zig error sets, going through libc if linking libc,
9877/// and with some footguns eliminated.
9878pub const wrapped = struct {
9879 pub const lfs64_abi = builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
9880 const system = if (builtin.link_libc) std.c else std.os.linux;
9881
9882 pub const SendfileError = std.posix.UnexpectedError || error{
9883 /// `out_fd` is an unconnected socket, or out_fd closed its read end.
9884 BrokenPipe,
9885 /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd.
9886 UnsupportedOperation,
9887 /// Nonblocking I/O has been selected but the write would block.
9888 WouldBlock,
9889 /// Unspecified error while reading from in_fd.
9890 InputOutput,
9891 /// Insufficient kernel memory to read from in_fd.
9892 SystemResources,
9893 /// `offset` is not `null` but the input file is not seekable.
9894 Unseekable,
9895 };
9896
9897 pub fn sendfile(
9898 out_fd: fd_t,
9899 in_fd: fd_t,
9900 in_offset: ?*off_t,
9901 in_len: usize,
9902 ) SendfileError!usize {
9903 const adjusted_len = @min(in_len, 0x7ffff000); // Prevents EOVERFLOW.
9904 const sendfileSymbol = if (lfs64_abi) system.sendfile64 else system.sendfile;
9905 const rc = sendfileSymbol(out_fd, in_fd, in_offset, adjusted_len);
9906 switch (system.errno(rc)) {
9907 .SUCCESS => return @intCast(rc),
9908 .BADF => return invalidApiUsage(), // Always a race condition.
9909 .FAULT => return invalidApiUsage(), // Segmentation fault.
9910 .OVERFLOW => return unexpectedErrno(.OVERFLOW), // We avoid passing too large of a `count`.
9911 .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
9912 .INVAL => return error.UnsupportedOperation,
9913 .AGAIN => return error.WouldBlock,
9914 .IO => return error.InputOutput,
9915 .PIPE => return error.BrokenPipe,
9916 .NOMEM => return error.SystemResources,
9917 .NXIO => return error.Unseekable,
9918 .SPIPE => return error.Unseekable,
9919 else => |err| return unexpectedErrno(err),
9920 }
9921 }
9922
9923 pub const CopyFileRangeError = std.posix.UnexpectedError || error{
9924 /// One of:
9925 /// * One or more file descriptors are not valid.
9926 /// * fd_in is not open for reading; or fd_out is not open for writing.
9927 /// * The O_APPEND flag is set for the open file description referred
9928 /// to by the file descriptor fd_out.
9929 BadFileFlags,
9930 /// One of:
9931 /// * An attempt was made to write at a position past the maximum file
9932 /// offset the kernel supports.
9933 /// * An attempt was made to write a range that exceeds the allowed
9934 /// maximum file size. The maximum file size differs between
9935 /// filesystem implementations and can be different from the maximum
9936 /// allowed file offset.
9937 /// * An attempt was made to write beyond the process's file size
9938 /// resource limit. This may also result in the process receiving a
9939 /// SIGXFSZ signal.
9940 FileTooBig,
9941 /// One of:
9942 /// * either fd_in or fd_out is not a regular file
9943 /// * flags argument is not zero
9944 /// * fd_in and fd_out refer to the same file and the source and target ranges overlap.
9945 InvalidArguments,
9946 /// A low-level I/O error occurred while copying.
9947 InputOutput,
9948 /// Either fd_in or fd_out refers to a directory.
9949 IsDir,
9950 OutOfMemory,
9951 /// There is not enough space on the target filesystem to complete the copy.
9952 NoSpaceLeft,
9953 /// (since Linux 5.19) the filesystem does not support this operation.
9954 OperationNotSupported,
9955 /// The requested source or destination range is too large to represent
9956 /// in the specified data types.
9957 Overflow,
9958 /// fd_out refers to an immutable file.
9959 PermissionDenied,
9960 /// Either fd_in or fd_out refers to an active swap file.
9961 SwapFile,
9962 /// The files referred to by fd_in and fd_out are not on the same
9963 /// filesystem, and the source and target filesystems are not of the
9964 /// same type, or do not support cross-filesystem copy.
9965 NotSameFileSystem,
9966 };
9967
9968 pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64, len: usize, flags: u32) CopyFileRangeError!usize {
9969 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 34, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 27, .patch = 0 });
9970 const sys = if (use_c) std.c else std.os.linux;
9971 const rc = sys.copy_file_range(fd_in, off_in, fd_out, off_out, len, flags);
9972 switch (sys.errno(rc)) {
9973 .SUCCESS => return @intCast(rc),
9974 .BADF => return error.BadFileFlags,
9975 .FBIG => return error.FileTooBig,
9976 .INVAL => return error.InvalidArguments,
9977 .IO => return error.InputOutput,
9978 .ISDIR => return error.IsDir,
9979 .NOMEM => return error.OutOfMemory,
9980 .NOSPC => return error.NoSpaceLeft,
9981 .OPNOTSUPP => return error.OperationNotSupported,
9982 .OVERFLOW => return error.Overflow,
9983 .PERM => return error.PermissionDenied,
9984 .TXTBSY => return error.SwapFile,
9985 .XDEV => return error.NotSameFileSystem,
9986 else => |err| return unexpectedErrno(err),
9987 }
9988 }
9989
9990 pub const StatxError = std.posix.UnexpectedError || error{
9991 /// Search permission is denied for one of the directories in `path`.
9992 AccessDenied,
9993 /// Too many symbolic links were encountered traversing `path`.
9994 SymLinkLoop,
9995 /// `path` is too long.
9996 NameTooLong,
9997 /// One of:
9998 /// - A component of `path` does not exist.
9999 /// - A component of `path` is not a directory.
10000 /// - `path` is a relative and `dirfd` is not a directory file descriptor.
10001 FileNotFound,
10002 /// Insufficient memory is available.
10003 SystemResources,
10004 };
10005
10006 pub fn statx(dirfd: fd_t, path: [*:0]const u8, flags: u32, mask: STATX) StatxError!Statx {
10007 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
10008 .{ .major = 30, .minor = 0, .patch = 0 }
10009 else
10010 .{ .major = 2, .minor = 28, .patch = 0 });
10011 const sys = if (use_c) std.c else std.os.linux;
10012
10013 var stx = std.mem.zeroes(Statx);
10014 const rc = sys.statx(dirfd, path, flags, mask, &stx);
10015 return switch (sys.errno(rc)) {
10016 .SUCCESS => stx,
10017 .ACCES => error.AccessDenied,
10018 .BADF => invalidApiUsage(),
10019 .FAULT => invalidApiUsage(),
10020 .INVAL => invalidApiUsage(),
10021 .LOOP => error.SymLinkLoop,
10022 .NAMETOOLONG => error.NameTooLong,
10023 .NOENT => error.FileNotFound,
10024 .NOTDIR => error.FileNotFound,
10025 .NOMEM => error.SystemResources,
10026 else => |err| unexpectedErrno(err),
10027 };
10028 }
10029
10030 const unexpectedErrno = std.posix.unexpectedErrno;
10031
10032 fn invalidApiUsage() error{Unexpected} {
10033 if (builtin.mode == .Debug) @panic("invalid API usage");
10034 return error.Unexpected;
10035 }
10036};
lib/std/os/linux/IoUring.zig+62-2769
......@@ -1,14 +1,17 @@
11const IoUring = @This();
2const std = @import("std");
2
33const builtin = @import("builtin");
4const is_linux = builtin.os.tag == .linux;
5
6const std = @import("../../std.zig");
7const Io = std.Io;
8const Allocator = std.mem.Allocator;
49const assert = std.debug.assert;
5const mem = std.mem;
6const net = std.Io.net;
710const posix = std.posix;
811const linux = std.os.linux;
912const testing = std.testing;
10const is_linux = builtin.os.tag == .linux;
1113const page_size_min = std.heap.page_size_min;
14const createSocketTestHarness = @import("IoUring/test.zig").createSocketTestHarness;
1215
1316fd: linux.fd_t = -1,
1417sq: SubmissionQueue,
......@@ -22,7 +25,7 @@ features: u32,
2225/// see https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L8027-L8050.
2326/// Matches the interface of io_uring_queue_init() in liburing.
2427pub fn init(entries: u16, flags: u32) !IoUring {
25 var params = mem.zeroInit(linux.io_uring_params, .{
28 var params = std.mem.zeroInit(linux.io_uring_params, .{
2629 .flags = flags,
2730 .sq_thread_idle = 1000,
2831 });
......@@ -1309,7 +1312,7 @@ pub fn unregister_buffers(self: *IoUring) !void {
13091312/// io_uring subsystem of the running kernel. The io_uring_probe contains the
13101313/// list of supported operations.
13111314pub fn get_probe(self: *IoUring) !linux.io_uring_probe {
1312 var probe = mem.zeroInit(linux.io_uring_probe, .{});
1315 var probe = std.mem.zeroInit(linux.io_uring_probe, .{});
13131316 const res = linux.io_uring_register(self.fd, .REGISTER_PROBE, &probe, probe.ops.len);
13141317 try handle_register_buf_ring_result(res);
13151318 return probe;
......@@ -1636,7 +1639,7 @@ pub const BufferGroup = struct {
16361639
16371640 pub fn init(
16381641 ring: *IoUring,
1639 allocator: mem.Allocator,
1642 allocator: Allocator,
16401643 group_id: u16,
16411644 buffer_size: u32,
16421645 buffers_count: u16,
......@@ -1670,7 +1673,7 @@ pub const BufferGroup = struct {
16701673 };
16711674 }
16721675
1673 pub fn deinit(self: *BufferGroup, allocator: mem.Allocator) void {
1676 pub fn deinit(self: *BufferGroup, allocator: Allocator) void {
16741677 free_buf_ring(self.ring.fd, self.br, self.buffers_count, self.group_id);
16751678 allocator.free(self.buffers);
16761679 allocator.free(self.heads);
......@@ -1695,7 +1698,7 @@ pub const BufferGroup = struct {
16951698 }
16961699
16971700 // Get buffer by id.
1698 fn get_by_id(self: *BufferGroup, buffer_id: u16) []u8 {
1701 pub fn get_by_id(self: *BufferGroup, buffer_id: u16) []u8 {
16991702 const pos = self.buffer_size * buffer_id;
17001703 return self.buffers[pos .. pos + self.buffer_size][self.heads[buffer_id]..];
17011704 }
......@@ -1764,7 +1767,7 @@ fn register_buf_ring(
17641767 group_id: u16,
17651768 flags: linux.io_uring_buf_reg.Flags,
17661769) !void {
1767 var reg = mem.zeroInit(linux.io_uring_buf_reg, .{
1770 var reg = std.mem.zeroInit(linux.io_uring_buf_reg, .{
17681771 .ring_addr = addr,
17691772 .ring_entries = entries,
17701773 .bgid = group_id,
......@@ -1781,7 +1784,7 @@ fn register_buf_ring(
17811784}
17821785
17831786fn unregister_buf_ring(fd: linux.fd_t, group_id: u16) !void {
1784 var reg = mem.zeroInit(linux.io_uring_buf_reg, .{
1787 var reg = std.mem.zeroInit(linux.io_uring_buf_reg, .{
17851788 .bgid = group_id,
17861789 });
17871790 const res = linux.io_uring_register(
......@@ -1848,731 +1851,13 @@ pub fn buf_ring_advance(br: *linux.io_uring_buf_ring, count: u16) void {
18481851 @atomicStore(u16, &br.tail, tail, .release);
18491852}
18501853
1851test "structs/offsets/entries" {
1852 if (!is_linux) return error.SkipZigTest;
1853
1854 try testing.expectEqual(@as(usize, 120), @sizeOf(linux.io_uring_params));
1855 try testing.expectEqual(@as(usize, 64), @sizeOf(linux.io_uring_sqe));
1856 try testing.expectEqual(@as(usize, 16), @sizeOf(linux.io_uring_cqe));
1857
1858 try testing.expectEqual(0, linux.IORING_OFF_SQ_RING);
1859 try testing.expectEqual(0x8000000, linux.IORING_OFF_CQ_RING);
1860 try testing.expectEqual(0x10000000, linux.IORING_OFF_SQES);
1861
1862 try testing.expectError(error.EntriesZero, IoUring.init(0, 0));
1863 try testing.expectError(error.EntriesNotPowerOfTwo, IoUring.init(3, 0));
1864}
1865
1866test "nop" {
1867 if (!is_linux) return error.SkipZigTest;
1868
1869 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1870 error.SystemOutdated => return error.SkipZigTest,
1871 error.PermissionDenied => return error.SkipZigTest,
1872 else => return err,
1873 };
1874 defer {
1875 ring.deinit();
1876 testing.expectEqual(@as(linux.fd_t, -1), ring.fd) catch @panic("test failed");
1877 }
1878
1879 const sqe = try ring.nop(0xaaaaaaaa);
1880 try testing.expectEqual(linux.io_uring_sqe{
1881 .opcode = .NOP,
1882 .flags = 0,
1883 .ioprio = 0,
1884 .fd = 0,
1885 .off = 0,
1886 .addr = 0,
1887 .len = 0,
1888 .rw_flags = 0,
1889 .user_data = 0xaaaaaaaa,
1890 .buf_index = 0,
1891 .personality = 0,
1892 .splice_fd_in = 0,
1893 .addr3 = 0,
1894 .resv = 0,
1895 }, sqe.*);
1896
1897 try testing.expectEqual(@as(u32, 0), ring.sq.sqe_head);
1898 try testing.expectEqual(@as(u32, 1), ring.sq.sqe_tail);
1899 try testing.expectEqual(@as(u32, 0), ring.sq.tail.*);
1900 try testing.expectEqual(@as(u32, 0), ring.cq.head.*);
1901 try testing.expectEqual(@as(u32, 1), ring.sq_ready());
1902 try testing.expectEqual(@as(u32, 0), ring.cq_ready());
1903
1904 try testing.expectEqual(@as(u32, 1), try ring.submit());
1905 try testing.expectEqual(@as(u32, 1), ring.sq.sqe_head);
1906 try testing.expectEqual(@as(u32, 1), ring.sq.sqe_tail);
1907 try testing.expectEqual(@as(u32, 1), ring.sq.tail.*);
1908 try testing.expectEqual(@as(u32, 0), ring.cq.head.*);
1909 try testing.expectEqual(@as(u32, 0), ring.sq_ready());
1910
1911 try testing.expectEqual(linux.io_uring_cqe{
1912 .user_data = 0xaaaaaaaa,
1913 .res = 0,
1914 .flags = 0,
1915 }, try ring.copy_cqe());
1916 try testing.expectEqual(@as(u32, 1), ring.cq.head.*);
1917 try testing.expectEqual(@as(u32, 0), ring.cq_ready());
1918
1919 const sqe_barrier = try ring.nop(0xbbbbbbbb);
1920 sqe_barrier.flags |= linux.IOSQE_IO_DRAIN;
1921 try testing.expectEqual(@as(u32, 1), try ring.submit());
1922 try testing.expectEqual(linux.io_uring_cqe{
1923 .user_data = 0xbbbbbbbb,
1924 .res = 0,
1925 .flags = 0,
1926 }, try ring.copy_cqe());
1927 try testing.expectEqual(@as(u32, 2), ring.sq.sqe_head);
1928 try testing.expectEqual(@as(u32, 2), ring.sq.sqe_tail);
1929 try testing.expectEqual(@as(u32, 2), ring.sq.tail.*);
1930 try testing.expectEqual(@as(u32, 2), ring.cq.head.*);
1931}
1932
1933test "readv" {
1934 if (!is_linux) return error.SkipZigTest;
1935
1936 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1937 error.SystemOutdated => return error.SkipZigTest,
1938 error.PermissionDenied => return error.SkipZigTest,
1939 else => return err,
1940 };
1941 defer ring.deinit();
1942
1943 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
1944 defer posix.close(fd);
1945
1946 // Linux Kernel 5.4 supports IORING_REGISTER_FILES but not sparse fd sets (i.e. an fd of -1).
1947 // Linux Kernel 5.5 adds support for sparse fd sets.
1948 // Compare:
1949 // https://github.com/torvalds/linux/blob/v5.4/fs/io_uring.c#L3119-L3124 vs
1950 // https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L6687-L6691
1951 // We therefore avoid stressing sparse fd sets here:
1952 var registered_fds = [_]linux.fd_t{0} ** 1;
1953 const fd_index = 0;
1954 registered_fds[fd_index] = fd;
1955 try ring.register_files(registered_fds[0..]);
1956
1957 var buffer = [_]u8{42} ** 128;
1958 var iovecs = [_]posix.iovec{posix.iovec{ .base = &buffer, .len = buffer.len }};
1959 const sqe = try ring.read(0xcccccccc, fd_index, .{ .iovecs = iovecs[0..] }, 0);
1960 try testing.expectEqual(linux.IORING_OP.READV, sqe.opcode);
1961 sqe.flags |= linux.IOSQE_FIXED_FILE;
1962
1963 try testing.expectError(error.SubmissionQueueFull, ring.nop(0));
1964 try testing.expectEqual(@as(u32, 1), try ring.submit());
1965 try testing.expectEqual(linux.io_uring_cqe{
1966 .user_data = 0xcccccccc,
1967 .res = buffer.len,
1968 .flags = 0,
1969 }, try ring.copy_cqe());
1970 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]);
1971
1972 try ring.unregister_files();
1973}
1974
1975test "writev/fsync/readv" {
1976 if (!is_linux) return error.SkipZigTest;
1977
1978 var ring = IoUring.init(4, 0) catch |err| switch (err) {
1979 error.SystemOutdated => return error.SkipZigTest,
1980 error.PermissionDenied => return error.SkipZigTest,
1981 else => return err,
1982 };
1983 defer ring.deinit();
1984
1985 var tmp = std.testing.tmpDir(.{});
1986 defer tmp.cleanup();
1987
1988 const path = "test_io_uring_writev_fsync_readv";
1989 const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true });
1990 defer file.close();
1991 const fd = file.handle;
1992
1993 const buffer_write = [_]u8{42} ** 128;
1994 const iovecs_write = [_]posix.iovec_const{
1995 posix.iovec_const{ .base = &buffer_write, .len = buffer_write.len },
1996 };
1997 var buffer_read = [_]u8{0} ** 128;
1998 var iovecs_read = [_]posix.iovec{
1999 posix.iovec{ .base = &buffer_read, .len = buffer_read.len },
2000 };
2001
2002 const sqe_writev = try ring.writev(0xdddddddd, fd, iovecs_write[0..], 17);
2003 try testing.expectEqual(linux.IORING_OP.WRITEV, sqe_writev.opcode);
2004 try testing.expectEqual(@as(u64, 17), sqe_writev.off);
2005 sqe_writev.flags |= linux.IOSQE_IO_LINK;
2006
2007 const sqe_fsync = try ring.fsync(0xeeeeeeee, fd, 0);
2008 try testing.expectEqual(linux.IORING_OP.FSYNC, sqe_fsync.opcode);
2009 try testing.expectEqual(fd, sqe_fsync.fd);
2010 sqe_fsync.flags |= linux.IOSQE_IO_LINK;
2011
2012 const sqe_readv = try ring.read(0xffffffff, fd, .{ .iovecs = iovecs_read[0..] }, 17);
2013 try testing.expectEqual(linux.IORING_OP.READV, sqe_readv.opcode);
2014 try testing.expectEqual(@as(u64, 17), sqe_readv.off);
2015
2016 try testing.expectEqual(@as(u32, 3), ring.sq_ready());
2017 try testing.expectEqual(@as(u32, 3), try ring.submit_and_wait(3));
2018 try testing.expectEqual(@as(u32, 0), ring.sq_ready());
2019 try testing.expectEqual(@as(u32, 3), ring.cq_ready());
2020
2021 try testing.expectEqual(linux.io_uring_cqe{
2022 .user_data = 0xdddddddd,
2023 .res = buffer_write.len,
2024 .flags = 0,
2025 }, try ring.copy_cqe());
2026 try testing.expectEqual(@as(u32, 2), ring.cq_ready());
2027
2028 try testing.expectEqual(linux.io_uring_cqe{
2029 .user_data = 0xeeeeeeee,
2030 .res = 0,
2031 .flags = 0,
2032 }, try ring.copy_cqe());
2033 try testing.expectEqual(@as(u32, 1), ring.cq_ready());
2034
2035 try testing.expectEqual(linux.io_uring_cqe{
2036 .user_data = 0xffffffff,
2037 .res = buffer_read.len,
2038 .flags = 0,
2039 }, try ring.copy_cqe());
2040 try testing.expectEqual(@as(u32, 0), ring.cq_ready());
2041
2042 try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]);
2043}
2044
2045test "write/read" {
2046 if (!is_linux) return error.SkipZigTest;
2047
2048 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2049 error.SystemOutdated => return error.SkipZigTest,
2050 error.PermissionDenied => return error.SkipZigTest,
2051 else => return err,
2052 };
2053 defer ring.deinit();
2054
2055 var tmp = std.testing.tmpDir(.{});
2056 defer tmp.cleanup();
2057 const path = "test_io_uring_write_read";
2058 const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true });
2059 defer file.close();
2060 const fd = file.handle;
2061
2062 const buffer_write = [_]u8{97} ** 20;
2063 var buffer_read = [_]u8{98} ** 20;
2064 const sqe_write = try ring.write(0x11111111, fd, buffer_write[0..], 10);
2065 try testing.expectEqual(linux.IORING_OP.WRITE, sqe_write.opcode);
2066 try testing.expectEqual(@as(u64, 10), sqe_write.off);
2067 sqe_write.flags |= linux.IOSQE_IO_LINK;
2068 const sqe_read = try ring.read(0x22222222, fd, .{ .buffer = buffer_read[0..] }, 10);
2069 try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode);
2070 try testing.expectEqual(@as(u64, 10), sqe_read.off);
2071 try testing.expectEqual(@as(u32, 2), try ring.submit());
2072
2073 const cqe_write = try ring.copy_cqe();
2074 const cqe_read = try ring.copy_cqe();
2075 // Prior to Linux Kernel 5.6 this is the only way to test for read/write support:
2076 // https://lwn.net/Articles/809820/
2077 if (cqe_write.err() == .INVAL) return error.SkipZigTest;
2078 if (cqe_read.err() == .INVAL) return error.SkipZigTest;
2079 try testing.expectEqual(linux.io_uring_cqe{
2080 .user_data = 0x11111111,
2081 .res = buffer_write.len,
2082 .flags = 0,
2083 }, cqe_write);
2084 try testing.expectEqual(linux.io_uring_cqe{
2085 .user_data = 0x22222222,
2086 .res = buffer_read.len,
2087 .flags = 0,
2088 }, cqe_read);
2089 try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]);
2090}
2091
2092test "splice/read" {
2093 if (!is_linux) return error.SkipZigTest;
2094
2095 var ring = IoUring.init(4, 0) catch |err| switch (err) {
2096 error.SystemOutdated => return error.SkipZigTest,
2097 error.PermissionDenied => return error.SkipZigTest,
2098 else => return err,
2099 };
2100 defer ring.deinit();
2101
2102 var tmp = std.testing.tmpDir(.{});
2103 const path_src = "test_io_uring_splice_src";
2104 const file_src = try tmp.dir.createFile(path_src, .{ .read = true, .truncate = true });
2105 defer file_src.close();
2106 const fd_src = file_src.handle;
2107
2108 const path_dst = "test_io_uring_splice_dst";
2109 const file_dst = try tmp.dir.createFile(path_dst, .{ .read = true, .truncate = true });
2110 defer file_dst.close();
2111 const fd_dst = file_dst.handle;
2112
2113 const buffer_write = [_]u8{97} ** 20;
2114 var buffer_read = [_]u8{98} ** 20;
2115 _ = try file_src.write(&buffer_write);
2116
2117 const fds = try posix.pipe();
2118 const pipe_offset: u64 = std.math.maxInt(u64);
2119
2120 const sqe_splice_to_pipe = try ring.splice(0x11111111, fd_src, 0, fds[1], pipe_offset, buffer_write.len);
2121 try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_to_pipe.opcode);
2122 try testing.expectEqual(@as(u64, 0), sqe_splice_to_pipe.addr);
2123 try testing.expectEqual(pipe_offset, sqe_splice_to_pipe.off);
2124 sqe_splice_to_pipe.flags |= linux.IOSQE_IO_LINK;
2125
2126 const sqe_splice_from_pipe = try ring.splice(0x22222222, fds[0], pipe_offset, fd_dst, 10, buffer_write.len);
2127 try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_from_pipe.opcode);
2128 try testing.expectEqual(pipe_offset, sqe_splice_from_pipe.addr);
2129 try testing.expectEqual(@as(u64, 10), sqe_splice_from_pipe.off);
2130 sqe_splice_from_pipe.flags |= linux.IOSQE_IO_LINK;
2131
2132 const sqe_read = try ring.read(0x33333333, fd_dst, .{ .buffer = buffer_read[0..] }, 10);
2133 try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode);
2134 try testing.expectEqual(@as(u64, 10), sqe_read.off);
2135 try testing.expectEqual(@as(u32, 3), try ring.submit());
2136
2137 const cqe_splice_to_pipe = try ring.copy_cqe();
2138 const cqe_splice_from_pipe = try ring.copy_cqe();
2139 const cqe_read = try ring.copy_cqe();
2140 // Prior to Linux Kernel 5.6 this is the only way to test for splice/read support:
2141 // https://lwn.net/Articles/809820/
2142 if (cqe_splice_to_pipe.err() == .INVAL) return error.SkipZigTest;
2143 if (cqe_splice_from_pipe.err() == .INVAL) return error.SkipZigTest;
2144 if (cqe_read.err() == .INVAL) return error.SkipZigTest;
2145 try testing.expectEqual(linux.io_uring_cqe{
2146 .user_data = 0x11111111,
2147 .res = buffer_write.len,
2148 .flags = 0,
2149 }, cqe_splice_to_pipe);
2150 try testing.expectEqual(linux.io_uring_cqe{
2151 .user_data = 0x22222222,
2152 .res = buffer_write.len,
2153 .flags = 0,
2154 }, cqe_splice_from_pipe);
2155 try testing.expectEqual(linux.io_uring_cqe{
2156 .user_data = 0x33333333,
2157 .res = buffer_read.len,
2158 .flags = 0,
2159 }, cqe_read);
2160 try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]);
2161}
2162
2163test "write_fixed/read_fixed" {
2164 if (!is_linux) return error.SkipZigTest;
2165
2166 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2167 error.SystemOutdated => return error.SkipZigTest,
2168 error.PermissionDenied => return error.SkipZigTest,
2169 else => return err,
2170 };
2171 defer ring.deinit();
2172
2173 var tmp = std.testing.tmpDir(.{});
2174 defer tmp.cleanup();
2175
2176 const path = "test_io_uring_write_read_fixed";
2177 const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true });
2178 defer file.close();
2179 const fd = file.handle;
2180
2181 var raw_buffers: [2][11]u8 = undefined;
2182 // First buffer will be written to the file.
2183 @memset(&raw_buffers[0], 'z');
2184 raw_buffers[0][0.."foobar".len].* = "foobar".*;
2185
2186 var buffers = [2]posix.iovec{
2187 .{ .base = &raw_buffers[0], .len = raw_buffers[0].len },
2188 .{ .base = &raw_buffers[1], .len = raw_buffers[1].len },
2189 };
2190 ring.register_buffers(&buffers) catch |err| switch (err) {
2191 error.SystemResources => {
2192 // See https://github.com/ziglang/zig/issues/15362
2193 return error.SkipZigTest;
2194 },
2195 else => |e| return e,
2196 };
2197
2198 const sqe_write = try ring.write_fixed(0x45454545, fd, &buffers[0], 3, 0);
2199 try testing.expectEqual(linux.IORING_OP.WRITE_FIXED, sqe_write.opcode);
2200 try testing.expectEqual(@as(u64, 3), sqe_write.off);
2201 sqe_write.flags |= linux.IOSQE_IO_LINK;
2202
2203 const sqe_read = try ring.read_fixed(0x12121212, fd, &buffers[1], 0, 1);
2204 try testing.expectEqual(linux.IORING_OP.READ_FIXED, sqe_read.opcode);
2205 try testing.expectEqual(@as(u64, 0), sqe_read.off);
2206
2207 try testing.expectEqual(@as(u32, 2), try ring.submit());
2208
2209 const cqe_write = try ring.copy_cqe();
2210 const cqe_read = try ring.copy_cqe();
2211
2212 try testing.expectEqual(linux.io_uring_cqe{
2213 .user_data = 0x45454545,
2214 .res = @as(i32, @intCast(buffers[0].len)),
2215 .flags = 0,
2216 }, cqe_write);
2217 try testing.expectEqual(linux.io_uring_cqe{
2218 .user_data = 0x12121212,
2219 .res = @as(i32, @intCast(buffers[1].len)),
2220 .flags = 0,
2221 }, cqe_read);
2222
2223 try testing.expectEqualSlices(u8, "\x00\x00\x00", buffers[1].base[0..3]);
2224 try testing.expectEqualSlices(u8, "foobar", buffers[1].base[3..9]);
2225 try testing.expectEqualSlices(u8, "zz", buffers[1].base[9..11]);
2226}
2227
2228test "openat" {
2229 if (!is_linux) return error.SkipZigTest;
2230
2231 var ring = IoUring.init(1, 0) catch |err| switch (err) {
2232 error.SystemOutdated => return error.SkipZigTest,
2233 error.PermissionDenied => return error.SkipZigTest,
2234 else => return err,
2235 };
2236 defer ring.deinit();
2237
2238 var tmp = std.testing.tmpDir(.{});
2239 defer tmp.cleanup();
2240
2241 const path = "test_io_uring_openat";
2242
2243 // Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014
2244 const path_addr = if (builtin.zig_backend == .stage2_llvm) p: {
2245 var workaround = path;
2246 _ = &workaround;
2247 break :p @intFromPtr(workaround);
2248 } else @intFromPtr(path);
2249
2250 const flags: linux.O = .{ .CLOEXEC = true, .ACCMODE = .RDWR, .CREAT = true };
2251 const mode: posix.mode_t = 0o666;
2252 const sqe_openat = try ring.openat(0x33333333, tmp.dir.fd, path, flags, mode);
2253 try testing.expectEqual(linux.io_uring_sqe{
2254 .opcode = .OPENAT,
2255 .flags = 0,
2256 .ioprio = 0,
2257 .fd = tmp.dir.fd,
2258 .off = 0,
2259 .addr = path_addr,
2260 .len = mode,
2261 .rw_flags = @bitCast(flags),
2262 .user_data = 0x33333333,
2263 .buf_index = 0,
2264 .personality = 0,
2265 .splice_fd_in = 0,
2266 .addr3 = 0,
2267 .resv = 0,
2268 }, sqe_openat.*);
2269 try testing.expectEqual(@as(u32, 1), try ring.submit());
2270
2271 const cqe_openat = try ring.copy_cqe();
2272 try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data);
2273 if (cqe_openat.err() == .INVAL) return error.SkipZigTest;
2274 if (cqe_openat.err() == .BADF) return error.SkipZigTest;
2275 if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res});
2276 try testing.expect(cqe_openat.res > 0);
2277 try testing.expectEqual(@as(u32, 0), cqe_openat.flags);
2278
2279 posix.close(cqe_openat.res);
2280}
2281
2282test "close" {
2283 if (!is_linux) return error.SkipZigTest;
2284
2285 var ring = IoUring.init(1, 0) catch |err| switch (err) {
2286 error.SystemOutdated => return error.SkipZigTest,
2287 error.PermissionDenied => return error.SkipZigTest,
2288 else => return err,
2289 };
2290 defer ring.deinit();
2291
2292 var tmp = std.testing.tmpDir(.{});
2293 defer tmp.cleanup();
2294
2295 const path = "test_io_uring_close";
2296 const file = try tmp.dir.createFile(path, .{});
2297 errdefer file.close();
2298
2299 const sqe_close = try ring.close(0x44444444, file.handle);
2300 try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode);
2301 try testing.expectEqual(file.handle, sqe_close.fd);
2302 try testing.expectEqual(@as(u32, 1), try ring.submit());
2303
2304 const cqe_close = try ring.copy_cqe();
2305 if (cqe_close.err() == .INVAL) return error.SkipZigTest;
2306 try testing.expectEqual(linux.io_uring_cqe{
2307 .user_data = 0x44444444,
2308 .res = 0,
2309 .flags = 0,
2310 }, cqe_close);
2311}
2312
2313test "accept/connect/send/recv" {
2314 if (!is_linux) return error.SkipZigTest;
2315
2316 var ring = IoUring.init(16, 0) catch |err| switch (err) {
2317 error.SystemOutdated => return error.SkipZigTest,
2318 error.PermissionDenied => return error.SkipZigTest,
2319 else => return err,
2320 };
2321 defer ring.deinit();
2322
2323 const socket_test_harness = try createSocketTestHarness(&ring);
2324 defer socket_test_harness.close();
2325
2326 const buffer_send = [_]u8{ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 };
2327 var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 };
2328
2329 const sqe_send = try ring.send(0xeeeeeeee, socket_test_harness.client, buffer_send[0..], 0);
2330 sqe_send.flags |= linux.IOSQE_IO_LINK;
2331 _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
2332 try testing.expectEqual(@as(u32, 2), try ring.submit());
2333
2334 const cqe_send = try ring.copy_cqe();
2335 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
2336 try testing.expectEqual(linux.io_uring_cqe{
2337 .user_data = 0xeeeeeeee,
2338 .res = buffer_send.len,
2339 .flags = 0,
2340 }, cqe_send);
2341
2342 const cqe_recv = try ring.copy_cqe();
2343 if (cqe_recv.err() == .INVAL) return error.SkipZigTest;
2344 try testing.expectEqual(linux.io_uring_cqe{
2345 .user_data = 0xffffffff,
2346 .res = buffer_recv.len,
2347 // ignore IORING_CQE_F_SOCK_NONEMPTY since it is only set on some systems
2348 .flags = cqe_recv.flags & linux.IORING_CQE_F_SOCK_NONEMPTY,
2349 }, cqe_recv);
2350
2351 try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]);
2352}
2353
2354test "sendmsg/recvmsg" {
2355 if (!is_linux) return error.SkipZigTest;
2356
2357 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2358 error.SystemOutdated => return error.SkipZigTest,
2359 error.PermissionDenied => return error.SkipZigTest,
2360 else => return err,
2361 };
2362 defer ring.deinit();
2363
2364 var address_server: linux.sockaddr.in = .{
2365 .port = 0,
2366 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
2367 };
2368
2369 const server = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0);
2370 defer posix.close(server);
2371 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1)));
2372 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
2373 try posix.bind(server, addrAny(&address_server), @sizeOf(linux.sockaddr.in));
2374
2375 // set address_server to the OS-chosen IP/port.
2376 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
2377 try posix.getsockname(server, addrAny(&address_server), &slen);
2378
2379 const client = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0);
2380 defer posix.close(client);
2381
2382 const buffer_send = [_]u8{42} ** 128;
2383 const iovecs_send = [_]posix.iovec_const{
2384 posix.iovec_const{ .base = &buffer_send, .len = buffer_send.len },
2385 };
2386 const msg_send: linux.msghdr_const = .{
2387 .name = addrAny(&address_server),
2388 .namelen = @sizeOf(linux.sockaddr.in),
2389 .iov = &iovecs_send,
2390 .iovlen = 1,
2391 .control = null,
2392 .controllen = 0,
2393 .flags = 0,
2394 };
2395 const sqe_sendmsg = try ring.sendmsg(0x11111111, client, &msg_send, 0);
2396 sqe_sendmsg.flags |= linux.IOSQE_IO_LINK;
2397 try testing.expectEqual(linux.IORING_OP.SENDMSG, sqe_sendmsg.opcode);
2398 try testing.expectEqual(client, sqe_sendmsg.fd);
2399
2400 var buffer_recv = [_]u8{0} ** 128;
2401 var iovecs_recv = [_]posix.iovec{
2402 posix.iovec{ .base = &buffer_recv, .len = buffer_recv.len },
2403 };
2404 var address_recv: linux.sockaddr.in = .{
2405 .port = 0,
2406 .addr = 0,
2407 };
2408 var msg_recv: linux.msghdr = .{
2409 .name = addrAny(&address_recv),
2410 .namelen = @sizeOf(linux.sockaddr.in),
2411 .iov = &iovecs_recv,
2412 .iovlen = 1,
2413 .control = null,
2414 .controllen = 0,
2415 .flags = 0,
2416 };
2417 const sqe_recvmsg = try ring.recvmsg(0x22222222, server, &msg_recv, 0);
2418 try testing.expectEqual(linux.IORING_OP.RECVMSG, sqe_recvmsg.opcode);
2419 try testing.expectEqual(server, sqe_recvmsg.fd);
2420
2421 try testing.expectEqual(@as(u32, 2), ring.sq_ready());
2422 try testing.expectEqual(@as(u32, 2), try ring.submit_and_wait(2));
2423 try testing.expectEqual(@as(u32, 0), ring.sq_ready());
2424 try testing.expectEqual(@as(u32, 2), ring.cq_ready());
2425
2426 const cqe_sendmsg = try ring.copy_cqe();
2427 if (cqe_sendmsg.res == -@as(i32, @intFromEnum(linux.E.INVAL))) return error.SkipZigTest;
2428 try testing.expectEqual(linux.io_uring_cqe{
2429 .user_data = 0x11111111,
2430 .res = buffer_send.len,
2431 .flags = 0,
2432 }, cqe_sendmsg);
2433
2434 const cqe_recvmsg = try ring.copy_cqe();
2435 if (cqe_recvmsg.res == -@as(i32, @intFromEnum(linux.E.INVAL))) return error.SkipZigTest;
2436 try testing.expectEqual(linux.io_uring_cqe{
2437 .user_data = 0x22222222,
2438 .res = buffer_recv.len,
2439 // ignore IORING_CQE_F_SOCK_NONEMPTY since it is set non-deterministically
2440 .flags = cqe_recvmsg.flags & linux.IORING_CQE_F_SOCK_NONEMPTY,
2441 }, cqe_recvmsg);
2442
2443 try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]);
2444}
2445
2446test "timeout (after a relative time)" {
1854test BufferGroup {
24471855 if (!is_linux) return error.SkipZigTest;
24481856
24491857 const io = testing.io;
1858 _ = io;
24501859
2451 var ring = IoUring.init(1, 0) catch |err| switch (err) {
2452 error.SystemOutdated => return error.SkipZigTest,
2453 error.PermissionDenied => return error.SkipZigTest,
2454 else => return err,
2455 };
2456 defer ring.deinit();
2457
2458 const ms = 10;
2459 const margin = 5;
2460 const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 };
2461
2462 const started = try std.Io.Clock.awake.now(io);
2463 const sqe = try ring.timeout(0x55555555, &ts, 0, 0);
2464 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);
2465 try testing.expectEqual(@as(u32, 1), try ring.submit());
2466 const cqe = try ring.copy_cqe();
2467 const stopped = try std.Io.Clock.awake.now(io);
2468
2469 try testing.expectEqual(linux.io_uring_cqe{
2470 .user_data = 0x55555555,
2471 .res = -@as(i32, @intFromEnum(linux.E.TIME)),
2472 .flags = 0,
2473 }, cqe);
2474
2475 // Tests should not depend on timings: skip test if outside margin.
2476 const ms_elapsed = started.durationTo(stopped).toMilliseconds();
2477 if (ms_elapsed > margin) return error.SkipZigTest;
2478}
2479
2480test "timeout (after a number of completions)" {
2481 if (!is_linux) return error.SkipZigTest;
2482
2483 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2484 error.SystemOutdated => return error.SkipZigTest,
2485 error.PermissionDenied => return error.SkipZigTest,
2486 else => return err,
2487 };
2488 defer ring.deinit();
2489
2490 const ts: linux.kernel_timespec = .{ .sec = 3, .nsec = 0 };
2491 const count_completions: u64 = 1;
2492 const sqe_timeout = try ring.timeout(0x66666666, &ts, count_completions, 0);
2493 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode);
2494 try testing.expectEqual(count_completions, sqe_timeout.off);
2495 _ = try ring.nop(0x77777777);
2496 try testing.expectEqual(@as(u32, 2), try ring.submit());
2497
2498 const cqe_nop = try ring.copy_cqe();
2499 try testing.expectEqual(linux.io_uring_cqe{
2500 .user_data = 0x77777777,
2501 .res = 0,
2502 .flags = 0,
2503 }, cqe_nop);
2504
2505 const cqe_timeout = try ring.copy_cqe();
2506 try testing.expectEqual(linux.io_uring_cqe{
2507 .user_data = 0x66666666,
2508 .res = 0,
2509 .flags = 0,
2510 }, cqe_timeout);
2511}
2512
2513test "timeout_remove" {
2514 if (!is_linux) return error.SkipZigTest;
2515
2516 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2517 error.SystemOutdated => return error.SkipZigTest,
2518 error.PermissionDenied => return error.SkipZigTest,
2519 else => return err,
2520 };
2521 defer ring.deinit();
2522
2523 const ts: linux.kernel_timespec = .{ .sec = 3, .nsec = 0 };
2524 const sqe_timeout = try ring.timeout(0x88888888, &ts, 0, 0);
2525 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode);
2526 try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout.user_data);
2527
2528 const sqe_timeout_remove = try ring.timeout_remove(0x99999999, 0x88888888, 0);
2529 try testing.expectEqual(linux.IORING_OP.TIMEOUT_REMOVE, sqe_timeout_remove.opcode);
2530 try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout_remove.addr);
2531 try testing.expectEqual(@as(u64, 0x99999999), sqe_timeout_remove.user_data);
2532
2533 try testing.expectEqual(@as(u32, 2), try ring.submit());
2534
2535 // The order in which the CQE arrive is not clearly documented and it changed with kernel 5.18:
2536 // * kernel 5.10 gives user data 0x88888888 first, 0x99999999 second
2537 // * kernel 5.18 gives user data 0x99999999 first, 0x88888888 second
2538
2539 var cqes: [2]linux.io_uring_cqe = undefined;
2540 cqes[0] = try ring.copy_cqe();
2541 cqes[1] = try ring.copy_cqe();
2542
2543 for (cqes) |cqe| {
2544 // IORING_OP_TIMEOUT_REMOVE is not supported by this kernel version:
2545 // Timeout remove operations set the fd to -1, which results in EBADF before EINVAL.
2546 // We use IORING_FEAT_RW_CUR_POS as a safety check here to make sure we are at least pre-5.6.
2547 // We don't want to skip this test for newer kernels.
2548 if (cqe.user_data == 0x99999999 and
2549 cqe.err() == .BADF and
2550 (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0)
2551 {
2552 return error.SkipZigTest;
2553 }
2554
2555 try testing.expect(cqe.user_data == 0x88888888 or cqe.user_data == 0x99999999);
2556
2557 if (cqe.user_data == 0x88888888) {
2558 try testing.expectEqual(linux.io_uring_cqe{
2559 .user_data = 0x88888888,
2560 .res = -@as(i32, @intFromEnum(linux.E.CANCELED)),
2561 .flags = 0,
2562 }, cqe);
2563 } else if (cqe.user_data == 0x99999999) {
2564 try testing.expectEqual(linux.io_uring_cqe{
2565 .user_data = 0x99999999,
2566 .res = 0,
2567 .flags = 0,
2568 }, cqe);
2569 }
2570 }
2571}
2572
2573test "accept/connect/recv/link_timeout" {
2574 if (!is_linux) return error.SkipZigTest;
2575
1860 // Init IoUring
25761861 var ring = IoUring.init(16, 0) catch |err| switch (err) {
25771862 error.SystemOutdated => return error.SkipZigTest,
25781863 error.PermissionDenied => return error.SkipZigTest,
......@@ -2580,2052 +1865,60 @@ test "accept/connect/recv/link_timeout" {
25801865 };
25811866 defer ring.deinit();
25821867
2583 const socket_test_harness = try createSocketTestHarness(&ring);
2584 defer socket_test_harness.close();
2585
2586 var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 };
2587
2588 const sqe_recv = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
2589 sqe_recv.flags |= linux.IOSQE_IO_LINK;
2590
2591 const ts = linux.kernel_timespec{ .sec = 0, .nsec = 1000000 };
2592 _ = try ring.link_timeout(0x22222222, &ts, 0);
2593
2594 const nr_wait = try ring.submit();
2595 try testing.expectEqual(@as(u32, 2), nr_wait);
2596
2597 var i: usize = 0;
2598 while (i < nr_wait) : (i += 1) {
2599 const cqe = try ring.copy_cqe();
2600 switch (cqe.user_data) {
2601 0xffffffff => {
2602 if (cqe.res != -@as(i32, @intFromEnum(linux.E.INTR)) and
2603 cqe.res != -@as(i32, @intFromEnum(linux.E.CANCELED)))
2604 {
2605 std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res });
2606 try testing.expect(false);
2607 }
2608 },
2609 0x22222222 => {
2610 if (cqe.res != -@as(i32, @intFromEnum(linux.E.ALREADY)) and
2611 cqe.res != -@as(i32, @intFromEnum(linux.E.TIME)))
2612 {
2613 std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res });
2614 try testing.expect(false);
2615 }
2616 },
2617 else => @panic("should not happen"),
2618 }
2619 }
2620}
2621
2622test "fallocate" {
2623 if (!is_linux) return error.SkipZigTest;
2624
2625 var ring = IoUring.init(1, 0) catch |err| switch (err) {
2626 error.SystemOutdated => return error.SkipZigTest,
2627 error.PermissionDenied => return error.SkipZigTest,
1868 // Init buffer group for ring
1869 const group_id: u16 = 1; // buffers group id
1870 const buffers_count: u16 = 1; // number of buffers in buffer group
1871 const buffer_size: usize = 128; // size of each buffer in group
1872 var buf_grp = BufferGroup.init(
1873 &ring,
1874 testing.allocator,
1875 group_id,
1876 buffer_size,
1877 buffers_count,
1878 ) catch |err| switch (err) {
1879 // kernel older than 5.19
1880 error.ArgumentsInvalid => return error.SkipZigTest,
26281881 else => return err,
26291882 };
2630 defer ring.deinit();
2631
2632 var tmp = std.testing.tmpDir(.{});
2633 defer tmp.cleanup();
2634
2635 const path = "test_io_uring_fallocate";
2636 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
2637 defer file.close();
2638
2639 try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
1883 defer buf_grp.deinit(testing.allocator);
26401884
2641 const len: u64 = 65536;
2642 const sqe = try ring.fallocate(0xaaaaaaaa, file.handle, 0, 0, len);
2643 try testing.expectEqual(linux.IORING_OP.FALLOCATE, sqe.opcode);
2644 try testing.expectEqual(file.handle, sqe.fd);
2645 try testing.expectEqual(@as(u32, 1), try ring.submit());
1885 // Create client/server fds
1886 const fds = try createSocketTestHarness(&ring);
1887 defer fds.close();
1888 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
26461889
2647 const cqe = try ring.copy_cqe();
2648 switch (cqe.err()) {
2649 .SUCCESS => {},
2650 // This kernel's io_uring does not yet implement fallocate():
2651 .INVAL => return error.SkipZigTest,
2652 // This kernel does not implement fallocate():
2653 .NOSYS => return error.SkipZigTest,
2654 // The filesystem containing the file referred to by fd does not support this operation;
2655 // or the mode is not supported by the filesystem containing the file referred to by fd:
2656 .OPNOTSUPP => return error.SkipZigTest,
2657 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1890 // Client sends data
1891 {
1892 _ = try ring.send(1, fds.client, data[0..], 0);
1893 const submitted = try ring.submit();
1894 try testing.expectEqual(1, submitted);
1895 const cqe_send = try ring.copy_cqe();
1896 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
1897 try testing.expectEqual(linux.io_uring_cqe{ .user_data = 1, .res = data.len, .flags = 0 }, cqe_send);
26581898 }
2659 try testing.expectEqual(linux.io_uring_cqe{
2660 .user_data = 0xaaaaaaaa,
2661 .res = 0,
2662 .flags = 0,
2663 }, cqe);
2664
2665 try testing.expectEqual(len, (try file.stat()).size);
2666}
2667
2668test "statx" {
2669 if (!is_linux) return error.SkipZigTest;
2670
2671 var ring = IoUring.init(1, 0) catch |err| switch (err) {
2672 error.SystemOutdated => return error.SkipZigTest,
2673 error.PermissionDenied => return error.SkipZigTest,
2674 else => return err,
2675 };
2676 defer ring.deinit();
2677
2678 var tmp = std.testing.tmpDir(.{});
2679 defer tmp.cleanup();
2680 const path = "test_io_uring_statx";
2681 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
2682 defer file.close();
2683
2684 try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
26851899
2686 try file.writeAll("foobar");
1900 // Server uses buffer group receive
1901 {
1902 // Submit recv operation, buffer will be chosen from buffer group
1903 _ = try buf_grp.recv(2, fds.server, 0);
1904 const submitted = try ring.submit();
1905 try testing.expectEqual(1, submitted);
26871906
2688 var buf: linux.Statx = undefined;
2689 const sqe = try ring.statx(
2690 0xaaaaaaaa,
2691 tmp.dir.fd,
2692 path,
2693 0,
2694 .{ .SIZE = true },
2695 &buf,
2696 );
2697 try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode);
2698 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
2699 try testing.expectEqual(@as(u32, 1), try ring.submit());
1907 // ... when we have completion for recv operation
1908 const cqe = try ring.copy_cqe();
1909 try testing.expectEqual(2, cqe.user_data); // matches submitted user_data
1910 try testing.expect(cqe.res >= 0); // success
1911 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
1912 try testing.expectEqual(data.len, @as(usize, @intCast(cqe.res))); // cqe.res holds received data len
27001913
2701 const cqe = try ring.copy_cqe();
2702 switch (cqe.err()) {
2703 .SUCCESS => {},
2704 // This kernel's io_uring does not yet implement statx():
2705 .INVAL => return error.SkipZigTest,
2706 // This kernel does not implement statx():
2707 .NOSYS => return error.SkipZigTest,
2708 // The filesystem containing the file referred to by fd does not support this operation;
2709 // or the mode is not supported by the filesystem containing the file referred to by fd:
2710 .OPNOTSUPP => return error.SkipZigTest,
2711 // not supported on older kernels (5.4)
2712 .BADF => return error.SkipZigTest,
2713 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1914 // Get buffer from pool
1915 const buf = try buf_grp.get(cqe);
1916 try testing.expectEqualSlices(u8, &data, buf);
1917 // Release buffer to the kernel when application is done with it
1918 try buf_grp.put(cqe);
27141919 }
2715 try testing.expectEqual(linux.io_uring_cqe{
2716 .user_data = 0xaaaaaaaa,
2717 .res = 0,
2718 .flags = 0,
2719 }, cqe);
2720
2721 try testing.expect(buf.mask.SIZE);
2722 try testing.expectEqual(@as(u64, 6), buf.size);
2723}
2724
2725test "accept/connect/recv/cancel" {
2726 if (!is_linux) return error.SkipZigTest;
2727
2728 var ring = IoUring.init(16, 0) catch |err| switch (err) {
2729 error.SystemOutdated => return error.SkipZigTest,
2730 error.PermissionDenied => return error.SkipZigTest,
2731 else => return err,
2732 };
2733 defer ring.deinit();
2734
2735 const socket_test_harness = try createSocketTestHarness(&ring);
2736 defer socket_test_harness.close();
2737
2738 var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 };
2739
2740 _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
2741 try testing.expectEqual(@as(u32, 1), try ring.submit());
2742
2743 const sqe_cancel = try ring.cancel(0x99999999, 0xffffffff, 0);
2744 try testing.expectEqual(linux.IORING_OP.ASYNC_CANCEL, sqe_cancel.opcode);
2745 try testing.expectEqual(@as(u64, 0xffffffff), sqe_cancel.addr);
2746 try testing.expectEqual(@as(u64, 0x99999999), sqe_cancel.user_data);
2747 try testing.expectEqual(@as(u32, 1), try ring.submit());
2748
2749 var cqe_recv = try ring.copy_cqe();
2750 if (cqe_recv.err() == .INVAL) return error.SkipZigTest;
2751 var cqe_cancel = try ring.copy_cqe();
2752 if (cqe_cancel.err() == .INVAL) return error.SkipZigTest;
2753
2754 // The recv/cancel CQEs may arrive in any order, the recv CQE will sometimes come first:
2755 if (cqe_recv.user_data == 0x99999999 and cqe_cancel.user_data == 0xffffffff) {
2756 const a = cqe_recv;
2757 const b = cqe_cancel;
2758 cqe_recv = b;
2759 cqe_cancel = a;
2760 }
2761
2762 try testing.expectEqual(linux.io_uring_cqe{
2763 .user_data = 0xffffffff,
2764 .res = -@as(i32, @intFromEnum(linux.E.CANCELED)),
2765 .flags = 0,
2766 }, cqe_recv);
2767
2768 try testing.expectEqual(linux.io_uring_cqe{
2769 .user_data = 0x99999999,
2770 .res = 0,
2771 .flags = 0,
2772 }, cqe_cancel);
2773}
2774
2775test "register_files_update" {
2776 if (!is_linux) return error.SkipZigTest;
2777
2778 var ring = IoUring.init(1, 0) catch |err| switch (err) {
2779 error.SystemOutdated => return error.SkipZigTest,
2780 error.PermissionDenied => return error.SkipZigTest,
2781 else => return err,
2782 };
2783 defer ring.deinit();
2784
2785 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
2786 defer posix.close(fd);
2787
2788 var registered_fds = [_]linux.fd_t{0} ** 2;
2789 const fd_index = 0;
2790 const fd_index2 = 1;
2791 registered_fds[fd_index] = fd;
2792 registered_fds[fd_index2] = -1;
2793
2794 ring.register_files(registered_fds[0..]) catch |err| switch (err) {
2795 // Happens when the kernel doesn't support sparse entry (-1) in the file descriptors array.
2796 error.FileDescriptorInvalid => return error.SkipZigTest,
2797 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2798 };
2799
2800 // Test IORING_REGISTER_FILES_UPDATE
2801 // Only available since Linux 5.5
2802
2803 const fd2 = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
2804 defer posix.close(fd2);
2805
2806 registered_fds[fd_index] = fd2;
2807 registered_fds[fd_index2] = -1;
2808 try ring.register_files_update(0, registered_fds[0..]);
2809
2810 var buffer = [_]u8{42} ** 128;
2811 {
2812 const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0);
2813 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
2814 sqe.flags |= linux.IOSQE_FIXED_FILE;
2815
2816 try testing.expectEqual(@as(u32, 1), try ring.submit());
2817 try testing.expectEqual(linux.io_uring_cqe{
2818 .user_data = 0xcccccccc,
2819 .res = buffer.len,
2820 .flags = 0,
2821 }, try ring.copy_cqe());
2822 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]);
2823 }
2824
2825 // Test with a non-zero offset
2826
2827 registered_fds[fd_index] = -1;
2828 registered_fds[fd_index2] = -1;
2829 try ring.register_files_update(1, registered_fds[1..]);
2830
2831 {
2832 // Next read should still work since fd_index in the registered file descriptors hasn't been updated yet.
2833 const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0);
2834 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
2835 sqe.flags |= linux.IOSQE_FIXED_FILE;
2836
2837 try testing.expectEqual(@as(u32, 1), try ring.submit());
2838 try testing.expectEqual(linux.io_uring_cqe{
2839 .user_data = 0xcccccccc,
2840 .res = buffer.len,
2841 .flags = 0,
2842 }, try ring.copy_cqe());
2843 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]);
2844 }
2845
2846 try ring.register_files_update(0, registered_fds[0..]);
2847
2848 {
2849 // Now this should fail since both fds are sparse (-1)
2850 const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0);
2851 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
2852 sqe.flags |= linux.IOSQE_FIXED_FILE;
2853
2854 try testing.expectEqual(@as(u32, 1), try ring.submit());
2855 const cqe = try ring.copy_cqe();
2856 try testing.expectEqual(linux.E.BADF, cqe.err());
2857 }
2858
2859 try ring.unregister_files();
2860}
2861
2862test "shutdown" {
2863 if (!is_linux) return error.SkipZigTest;
2864
2865 var ring = IoUring.init(16, 0) catch |err| switch (err) {
2866 error.SystemOutdated => return error.SkipZigTest,
2867 error.PermissionDenied => return error.SkipZigTest,
2868 else => return err,
2869 };
2870 defer ring.deinit();
2871
2872 var address: linux.sockaddr.in = .{
2873 .port = 0,
2874 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
2875 };
2876
2877 // Socket bound, expect shutdown to work
2878 {
2879 const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
2880 defer posix.close(server);
2881 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
2882 try posix.bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in));
2883 try posix.listen(server, 1);
2884
2885 // set address to the OS-chosen IP/port.
2886 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
2887 try posix.getsockname(server, addrAny(&address), &slen);
2888
2889 const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD);
2890 try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode);
2891 try testing.expectEqual(@as(i32, server), shutdown_sqe.fd);
2892
2893 try testing.expectEqual(@as(u32, 1), try ring.submit());
2894
2895 const cqe = try ring.copy_cqe();
2896 switch (cqe.err()) {
2897 .SUCCESS => {},
2898 // This kernel's io_uring does not yet implement shutdown (kernel version < 5.11)
2899 .INVAL => return error.SkipZigTest,
2900 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2901 }
2902
2903 try testing.expectEqual(linux.io_uring_cqe{
2904 .user_data = 0x445445445,
2905 .res = 0,
2906 .flags = 0,
2907 }, cqe);
2908 }
2909
2910 // Socket not bound, expect to fail with ENOTCONN
2911 {
2912 const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
2913 defer posix.close(server);
2914
2915 const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) {
2916 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2917 };
2918 try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode);
2919 try testing.expectEqual(@as(i32, server), shutdown_sqe.fd);
2920
2921 try testing.expectEqual(@as(u32, 1), try ring.submit());
2922
2923 const cqe = try ring.copy_cqe();
2924 try testing.expectEqual(@as(u64, 0x445445445), cqe.user_data);
2925 try testing.expectEqual(linux.E.NOTCONN, cqe.err());
2926 }
2927}
2928
2929test "renameat" {
2930 if (!is_linux) return error.SkipZigTest;
2931
2932 var ring = IoUring.init(1, 0) catch |err| switch (err) {
2933 error.SystemOutdated => return error.SkipZigTest,
2934 error.PermissionDenied => return error.SkipZigTest,
2935 else => return err,
2936 };
2937 defer ring.deinit();
2938
2939 const old_path = "test_io_uring_renameat_old";
2940 const new_path = "test_io_uring_renameat_new";
2941
2942 var tmp = std.testing.tmpDir(.{});
2943 defer tmp.cleanup();
2944
2945 // Write old file with data
2946
2947 const old_file = try tmp.dir.createFile(old_path, .{ .truncate = true, .mode = 0o666 });
2948 defer old_file.close();
2949 try old_file.writeAll("hello");
2950
2951 // Submit renameat
2952
2953 const sqe = try ring.renameat(
2954 0x12121212,
2955 tmp.dir.fd,
2956 old_path,
2957 tmp.dir.fd,
2958 new_path,
2959 0,
2960 );
2961 try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode);
2962 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
2963 try testing.expectEqual(@as(i32, tmp.dir.fd), @as(i32, @bitCast(sqe.len)));
2964 try testing.expectEqual(@as(u32, 1), try ring.submit());
2965
2966 const cqe = try ring.copy_cqe();
2967 switch (cqe.err()) {
2968 .SUCCESS => {},
2969 // This kernel's io_uring does not yet implement renameat (kernel version < 5.11)
2970 .BADF, .INVAL => return error.SkipZigTest,
2971 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2972 }
2973 try testing.expectEqual(linux.io_uring_cqe{
2974 .user_data = 0x12121212,
2975 .res = 0,
2976 .flags = 0,
2977 }, cqe);
2978
2979 // Validate that the old file doesn't exist anymore
2980 try testing.expectError(error.FileNotFound, tmp.dir.openFile(old_path, .{}));
2981
2982 // Validate that the new file exists with the proper content
2983 var new_file_data: [16]u8 = undefined;
2984 try testing.expectEqualStrings("hello", try tmp.dir.readFile(new_path, &new_file_data));
2985}
2986
2987test "unlinkat" {
2988 if (!is_linux) return error.SkipZigTest;
2989
2990 var ring = IoUring.init(1, 0) catch |err| switch (err) {
2991 error.SystemOutdated => return error.SkipZigTest,
2992 error.PermissionDenied => return error.SkipZigTest,
2993 else => return err,
2994 };
2995 defer ring.deinit();
2996
2997 const path = "test_io_uring_unlinkat";
2998
2999 var tmp = std.testing.tmpDir(.{});
3000 defer tmp.cleanup();
3001
3002 // Write old file with data
3003
3004 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
3005 defer file.close();
3006
3007 // Submit unlinkat
3008
3009 const sqe = try ring.unlinkat(
3010 0x12121212,
3011 tmp.dir.fd,
3012 path,
3013 0,
3014 );
3015 try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode);
3016 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
3017 try testing.expectEqual(@as(u32, 1), try ring.submit());
3018
3019 const cqe = try ring.copy_cqe();
3020 switch (cqe.err()) {
3021 .SUCCESS => {},
3022 // This kernel's io_uring does not yet implement unlinkat (kernel version < 5.11)
3023 .BADF, .INVAL => return error.SkipZigTest,
3024 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3025 }
3026 try testing.expectEqual(linux.io_uring_cqe{
3027 .user_data = 0x12121212,
3028 .res = 0,
3029 .flags = 0,
3030 }, cqe);
3031
3032 // Validate that the file doesn't exist anymore
3033 _ = tmp.dir.openFile(path, .{}) catch |err| switch (err) {
3034 error.FileNotFound => {},
3035 else => std.debug.panic("unexpected error: {}", .{err}),
3036 };
3037}
3038
3039test "mkdirat" {
3040 if (!is_linux) return error.SkipZigTest;
3041
3042 var ring = IoUring.init(1, 0) catch |err| switch (err) {
3043 error.SystemOutdated => return error.SkipZigTest,
3044 error.PermissionDenied => return error.SkipZigTest,
3045 else => return err,
3046 };
3047 defer ring.deinit();
3048
3049 var tmp = std.testing.tmpDir(.{});
3050 defer tmp.cleanup();
3051
3052 const path = "test_io_uring_mkdirat";
3053
3054 // Submit mkdirat
3055
3056 const sqe = try ring.mkdirat(
3057 0x12121212,
3058 tmp.dir.fd,
3059 path,
3060 0o0755,
3061 );
3062 try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode);
3063 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
3064 try testing.expectEqual(@as(u32, 1), try ring.submit());
3065
3066 const cqe = try ring.copy_cqe();
3067 switch (cqe.err()) {
3068 .SUCCESS => {},
3069 // This kernel's io_uring does not yet implement mkdirat (kernel version < 5.15)
3070 .BADF, .INVAL => return error.SkipZigTest,
3071 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3072 }
3073 try testing.expectEqual(linux.io_uring_cqe{
3074 .user_data = 0x12121212,
3075 .res = 0,
3076 .flags = 0,
3077 }, cqe);
3078
3079 // Validate that the directory exist
3080 _ = try tmp.dir.openDir(path, .{});
3081}
3082
3083test "symlinkat" {
3084 if (!is_linux) return error.SkipZigTest;
3085
3086 var ring = IoUring.init(1, 0) catch |err| switch (err) {
3087 error.SystemOutdated => return error.SkipZigTest,
3088 error.PermissionDenied => return error.SkipZigTest,
3089 else => return err,
3090 };
3091 defer ring.deinit();
3092
3093 var tmp = std.testing.tmpDir(.{});
3094 defer tmp.cleanup();
3095
3096 const path = "test_io_uring_symlinkat";
3097 const link_path = "test_io_uring_symlinkat_link";
3098
3099 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
3100 defer file.close();
3101
3102 // Submit symlinkat
3103
3104 const sqe = try ring.symlinkat(
3105 0x12121212,
3106 path,
3107 tmp.dir.fd,
3108 link_path,
3109 );
3110 try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode);
3111 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
3112 try testing.expectEqual(@as(u32, 1), try ring.submit());
3113
3114 const cqe = try ring.copy_cqe();
3115 switch (cqe.err()) {
3116 .SUCCESS => {},
3117 // This kernel's io_uring does not yet implement symlinkat (kernel version < 5.15)
3118 .BADF, .INVAL => return error.SkipZigTest,
3119 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3120 }
3121 try testing.expectEqual(linux.io_uring_cqe{
3122 .user_data = 0x12121212,
3123 .res = 0,
3124 .flags = 0,
3125 }, cqe);
3126
3127 // Validate that the symlink exist
3128 _ = try tmp.dir.openFile(link_path, .{});
3129}
3130
3131test "linkat" {
3132 if (!is_linux) return error.SkipZigTest;
3133
3134 var ring = IoUring.init(1, 0) catch |err| switch (err) {
3135 error.SystemOutdated => return error.SkipZigTest,
3136 error.PermissionDenied => return error.SkipZigTest,
3137 else => return err,
3138 };
3139 defer ring.deinit();
3140
3141 var tmp = std.testing.tmpDir(.{});
3142 defer tmp.cleanup();
3143
3144 const first_path = "test_io_uring_linkat_first";
3145 const second_path = "test_io_uring_linkat_second";
3146
3147 // Write file with data
3148
3149 const first_file = try tmp.dir.createFile(first_path, .{ .truncate = true, .mode = 0o666 });
3150 defer first_file.close();
3151 try first_file.writeAll("hello");
3152
3153 // Submit linkat
3154
3155 const sqe = try ring.linkat(
3156 0x12121212,
3157 tmp.dir.fd,
3158 first_path,
3159 tmp.dir.fd,
3160 second_path,
3161 0,
3162 );
3163 try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode);
3164 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
3165 try testing.expectEqual(@as(i32, tmp.dir.fd), @as(i32, @bitCast(sqe.len)));
3166 try testing.expectEqual(@as(u32, 1), try ring.submit());
3167
3168 const cqe = try ring.copy_cqe();
3169 switch (cqe.err()) {
3170 .SUCCESS => {},
3171 // This kernel's io_uring does not yet implement linkat (kernel version < 5.15)
3172 .BADF, .INVAL => return error.SkipZigTest,
3173 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3174 }
3175 try testing.expectEqual(linux.io_uring_cqe{
3176 .user_data = 0x12121212,
3177 .res = 0,
3178 .flags = 0,
3179 }, cqe);
3180
3181 // Validate the second file
3182 var second_file_data: [16]u8 = undefined;
3183 try testing.expectEqualStrings("hello", try tmp.dir.readFile(second_path, &second_file_data));
3184}
3185
3186test "provide_buffers: read" {
3187 if (!is_linux) return error.SkipZigTest;
3188
3189 var ring = IoUring.init(1, 0) catch |err| switch (err) {
3190 error.SystemOutdated => return error.SkipZigTest,
3191 error.PermissionDenied => return error.SkipZigTest,
3192 else => return err,
3193 };
3194 defer ring.deinit();
3195
3196 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
3197 defer posix.close(fd);
3198
3199 const group_id = 1337;
3200 const buffer_id = 0;
3201
3202 const buffer_len = 128;
3203
3204 var buffers: [4][buffer_len]u8 = undefined;
3205
3206 // Provide 4 buffers
3207
3208 {
3209 const sqe = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id);
3210 try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode);
3211 try testing.expectEqual(@as(i32, buffers.len), sqe.fd);
3212 try testing.expectEqual(@as(u32, buffers[0].len), sqe.len);
3213 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3214 try testing.expectEqual(@as(u32, 1), try ring.submit());
3215
3216 const cqe = try ring.copy_cqe();
3217 switch (cqe.err()) {
3218 // Happens when the kernel is < 5.7
3219 .INVAL, .BADF => return error.SkipZigTest,
3220 .SUCCESS => {},
3221 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3222 }
3223 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
3224 }
3225
3226 // Do 4 reads which should consume all buffers
3227
3228 var i: usize = 0;
3229 while (i < buffers.len) : (i += 1) {
3230 const sqe = try ring.read(0xdededede, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3231 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
3232 try testing.expectEqual(@as(i32, fd), sqe.fd);
3233 try testing.expectEqual(@as(u64, 0), sqe.addr);
3234 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3235 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3236 try testing.expectEqual(@as(u32, 1), try ring.submit());
3237
3238 const cqe = try ring.copy_cqe();
3239 switch (cqe.err()) {
3240 .SUCCESS => {},
3241 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3242 }
3243
3244 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
3245 const used_buffer_id = cqe.flags >> 16;
3246 try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3);
3247 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
3248
3249 try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data);
3250 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]);
3251 }
3252
3253 // This read should fail
3254
3255 {
3256 const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3257 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
3258 try testing.expectEqual(@as(i32, fd), sqe.fd);
3259 try testing.expectEqual(@as(u64, 0), sqe.addr);
3260 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3261 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3262 try testing.expectEqual(@as(u32, 1), try ring.submit());
3263
3264 const cqe = try ring.copy_cqe();
3265 switch (cqe.err()) {
3266 // Expected
3267 .NOBUFS => {},
3268 .SUCCESS => std.debug.panic("unexpected success", .{}),
3269 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3270 }
3271 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
3272 }
3273
3274 // Provide 1 buffer again
3275
3276 // Deliberately put something we don't expect in the buffers
3277 @memset(mem.sliceAsBytes(&buffers), 42);
3278
3279 const reprovided_buffer_id = 2;
3280
3281 {
3282 _ = try ring.provide_buffers(0xabababab, @as([*]u8, @ptrCast(&buffers[reprovided_buffer_id])), buffer_len, 1, group_id, reprovided_buffer_id);
3283 try testing.expectEqual(@as(u32, 1), try ring.submit());
3284
3285 const cqe = try ring.copy_cqe();
3286 switch (cqe.err()) {
3287 .SUCCESS => {},
3288 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3289 }
3290 }
3291
3292 // Final read which should work
3293
3294 {
3295 const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3296 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
3297 try testing.expectEqual(@as(i32, fd), sqe.fd);
3298 try testing.expectEqual(@as(u64, 0), sqe.addr);
3299 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3300 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3301 try testing.expectEqual(@as(u32, 1), try ring.submit());
3302
3303 const cqe = try ring.copy_cqe();
3304 switch (cqe.err()) {
3305 .SUCCESS => {},
3306 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3307 }
3308
3309 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
3310 const used_buffer_id = cqe.flags >> 16;
3311 try testing.expectEqual(used_buffer_id, reprovided_buffer_id);
3312 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
3313 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
3314 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]);
3315 }
3316}
3317
3318test "remove_buffers" {
3319 if (!is_linux) return error.SkipZigTest;
3320
3321 var ring = IoUring.init(1, 0) catch |err| switch (err) {
3322 error.SystemOutdated => return error.SkipZigTest,
3323 error.PermissionDenied => return error.SkipZigTest,
3324 else => return err,
3325 };
3326 defer ring.deinit();
3327
3328 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
3329 defer posix.close(fd);
3330
3331 const group_id = 1337;
3332 const buffer_id = 0;
3333
3334 const buffer_len = 128;
3335
3336 var buffers: [4][buffer_len]u8 = undefined;
3337
3338 // Provide 4 buffers
3339
3340 {
3341 _ = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id);
3342 try testing.expectEqual(@as(u32, 1), try ring.submit());
3343
3344 const cqe = try ring.copy_cqe();
3345 switch (cqe.err()) {
3346 .INVAL, .BADF => return error.SkipZigTest,
3347 .SUCCESS => {},
3348 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3349 }
3350 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
3351 }
3352
3353 // Remove 3 buffers
3354
3355 {
3356 const sqe = try ring.remove_buffers(0xbababababa, 3, group_id);
3357 try testing.expectEqual(linux.IORING_OP.REMOVE_BUFFERS, sqe.opcode);
3358 try testing.expectEqual(@as(i32, 3), sqe.fd);
3359 try testing.expectEqual(@as(u64, 0), sqe.addr);
3360 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3361 try testing.expectEqual(@as(u32, 1), try ring.submit());
3362
3363 const cqe = try ring.copy_cqe();
3364 switch (cqe.err()) {
3365 .SUCCESS => {},
3366 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3367 }
3368 try testing.expectEqual(@as(u64, 0xbababababa), cqe.user_data);
3369 }
3370
3371 // This read should work
3372
3373 {
3374 _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3375 try testing.expectEqual(@as(u32, 1), try ring.submit());
3376
3377 const cqe = try ring.copy_cqe();
3378 switch (cqe.err()) {
3379 .SUCCESS => {},
3380 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3381 }
3382
3383 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
3384 const used_buffer_id = cqe.flags >> 16;
3385 try testing.expect(used_buffer_id >= 0 and used_buffer_id < 4);
3386 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
3387 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
3388 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]);
3389 }
3390
3391 // Final read should _not_ work
3392
3393 {
3394 _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3395 try testing.expectEqual(@as(u32, 1), try ring.submit());
3396
3397 const cqe = try ring.copy_cqe();
3398 switch (cqe.err()) {
3399 // Expected
3400 .NOBUFS => {},
3401 .SUCCESS => std.debug.panic("unexpected success", .{}),
3402 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3403 }
3404 }
3405}
3406
3407test "provide_buffers: accept/connect/send/recv" {
3408 if (!is_linux) return error.SkipZigTest;
3409
3410 var ring = IoUring.init(16, 0) catch |err| switch (err) {
3411 error.SystemOutdated => return error.SkipZigTest,
3412 error.PermissionDenied => return error.SkipZigTest,
3413 else => return err,
3414 };
3415 defer ring.deinit();
3416
3417 const group_id = 1337;
3418 const buffer_id = 0;
3419
3420 const buffer_len = 128;
3421 var buffers: [4][buffer_len]u8 = undefined;
3422
3423 // Provide 4 buffers
3424
3425 {
3426 const sqe = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id);
3427 try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode);
3428 try testing.expectEqual(@as(i32, buffers.len), sqe.fd);
3429 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3430 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3431 try testing.expectEqual(@as(u32, 1), try ring.submit());
3432
3433 const cqe = try ring.copy_cqe();
3434 switch (cqe.err()) {
3435 // Happens when the kernel is < 5.7
3436 .INVAL => return error.SkipZigTest,
3437 // Happens on the kernel 5.4
3438 .BADF => return error.SkipZigTest,
3439 .SUCCESS => {},
3440 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3441 }
3442 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
3443 }
3444
3445 const socket_test_harness = try createSocketTestHarness(&ring);
3446 defer socket_test_harness.close();
3447
3448 // Do 4 send on the socket
3449
3450 {
3451 var i: usize = 0;
3452 while (i < buffers.len) : (i += 1) {
3453 _ = try ring.send(0xdeaddead, socket_test_harness.server, &([_]u8{'z'} ** buffer_len), 0);
3454 try testing.expectEqual(@as(u32, 1), try ring.submit());
3455 }
3456
3457 var cqes: [4]linux.io_uring_cqe = undefined;
3458 try testing.expectEqual(@as(u32, 4), try ring.copy_cqes(&cqes, 4));
3459 }
3460
3461 // Do 4 recv which should consume all buffers
3462
3463 // Deliberately put something we don't expect in the buffers
3464 @memset(mem.sliceAsBytes(&buffers), 1);
3465
3466 var i: usize = 0;
3467 while (i < buffers.len) : (i += 1) {
3468 const sqe = try ring.recv(0xdededede, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3469 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
3470 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
3471 try testing.expectEqual(@as(u64, 0), sqe.addr);
3472 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3473 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3474 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
3475 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
3476 try testing.expectEqual(@as(u32, 1), try ring.submit());
3477
3478 const cqe = try ring.copy_cqe();
3479 switch (cqe.err()) {
3480 .SUCCESS => {},
3481 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3482 }
3483
3484 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
3485 const used_buffer_id = cqe.flags >> 16;
3486 try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3);
3487 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
3488
3489 try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data);
3490 const buffer = buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))];
3491 try testing.expectEqualSlices(u8, &([_]u8{'z'} ** buffer_len), buffer);
3492 }
3493
3494 // This recv should fail
3495
3496 {
3497 const sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3498 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
3499 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
3500 try testing.expectEqual(@as(u64, 0), sqe.addr);
3501 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3502 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3503 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
3504 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
3505 try testing.expectEqual(@as(u32, 1), try ring.submit());
3506
3507 const cqe = try ring.copy_cqe();
3508 switch (cqe.err()) {
3509 // Expected
3510 .NOBUFS => {},
3511 .SUCCESS => std.debug.panic("unexpected success", .{}),
3512 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3513 }
3514 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
3515 }
3516
3517 // Provide 1 buffer again
3518
3519 const reprovided_buffer_id = 2;
3520
3521 {
3522 _ = try ring.provide_buffers(0xabababab, @as([*]u8, @ptrCast(&buffers[reprovided_buffer_id])), buffer_len, 1, group_id, reprovided_buffer_id);
3523 try testing.expectEqual(@as(u32, 1), try ring.submit());
3524
3525 const cqe = try ring.copy_cqe();
3526 switch (cqe.err()) {
3527 .SUCCESS => {},
3528 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3529 }
3530 }
3531
3532 // Redo 1 send on the server socket
3533
3534 {
3535 _ = try ring.send(0xdeaddead, socket_test_harness.server, &([_]u8{'w'} ** buffer_len), 0);
3536 try testing.expectEqual(@as(u32, 1), try ring.submit());
3537
3538 _ = try ring.copy_cqe();
3539 }
3540
3541 // Final recv which should work
3542
3543 // Deliberately put something we don't expect in the buffers
3544 @memset(mem.sliceAsBytes(&buffers), 1);
3545
3546 {
3547 const sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
3548 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
3549 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
3550 try testing.expectEqual(@as(u64, 0), sqe.addr);
3551 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
3552 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
3553 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
3554 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
3555 try testing.expectEqual(@as(u32, 1), try ring.submit());
3556
3557 const cqe = try ring.copy_cqe();
3558 switch (cqe.err()) {
3559 .SUCCESS => {},
3560 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3561 }
3562
3563 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
3564 const used_buffer_id = cqe.flags >> 16;
3565 try testing.expectEqual(used_buffer_id, reprovided_buffer_id);
3566 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
3567 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
3568 const buffer = buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))];
3569 try testing.expectEqualSlices(u8, &([_]u8{'w'} ** buffer_len), buffer);
3570 }
3571}
3572
3573/// Used for testing server/client interactions.
3574const SocketTestHarness = struct {
3575 listener: posix.socket_t,
3576 server: posix.socket_t,
3577 client: posix.socket_t,
3578
3579 fn close(self: SocketTestHarness) void {
3580 posix.close(self.client);
3581 posix.close(self.listener);
3582 }
3583};
3584
3585fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
3586 // Create a TCP server socket
3587 var address: linux.sockaddr.in = .{
3588 .port = 0,
3589 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
3590 };
3591 const listener_socket = try createListenerSocket(&address);
3592 errdefer posix.close(listener_socket);
3593
3594 // Submit 1 accept
3595 var accept_addr: posix.sockaddr = undefined;
3596 var accept_addr_len: posix.socklen_t = @sizeOf(@TypeOf(accept_addr));
3597 _ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0);
3598
3599 // Create a TCP client socket
3600 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3601 errdefer posix.close(client);
3602 _ = try ring.connect(0xcccccccc, client, addrAny(&address), @sizeOf(linux.sockaddr.in));
3603
3604 try testing.expectEqual(@as(u32, 2), try ring.submit());
3605
3606 var cqe_accept = try ring.copy_cqe();
3607 if (cqe_accept.err() == .INVAL) return error.SkipZigTest;
3608 var cqe_connect = try ring.copy_cqe();
3609 if (cqe_connect.err() == .INVAL) return error.SkipZigTest;
3610
3611 // The accept/connect CQEs may arrive in any order, the connect CQE will sometimes come first:
3612 if (cqe_accept.user_data == 0xcccccccc and cqe_connect.user_data == 0xaaaaaaaa) {
3613 const a = cqe_accept;
3614 const b = cqe_connect;
3615 cqe_accept = b;
3616 cqe_connect = a;
3617 }
3618
3619 try testing.expectEqual(@as(u64, 0xaaaaaaaa), cqe_accept.user_data);
3620 if (cqe_accept.res <= 0) std.debug.print("\ncqe_accept.res={}\n", .{cqe_accept.res});
3621 try testing.expect(cqe_accept.res > 0);
3622 try testing.expectEqual(@as(u32, 0), cqe_accept.flags);
3623 try testing.expectEqual(linux.io_uring_cqe{
3624 .user_data = 0xcccccccc,
3625 .res = 0,
3626 .flags = 0,
3627 }, cqe_connect);
3628
3629 // All good
3630
3631 return SocketTestHarness{
3632 .listener = listener_socket,
3633 .server = cqe_accept.res,
3634 .client = client,
3635 };
3636}
3637
3638fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t {
3639 const kernel_backlog = 1;
3640 const listener_socket = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3641 errdefer posix.close(listener_socket);
3642
3643 try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
3644 try posix.bind(listener_socket, addrAny(address), @sizeOf(linux.sockaddr.in));
3645 try posix.listen(listener_socket, kernel_backlog);
3646
3647 // set address to the OS-chosen IP/port.
3648 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
3649 try posix.getsockname(listener_socket, addrAny(address), &slen);
3650
3651 return listener_socket;
3652}
3653
3654test "accept multishot" {
3655 if (!is_linux) return error.SkipZigTest;
3656
3657 var ring = IoUring.init(16, 0) catch |err| switch (err) {
3658 error.SystemOutdated => return error.SkipZigTest,
3659 error.PermissionDenied => return error.SkipZigTest,
3660 else => return err,
3661 };
3662 defer ring.deinit();
3663
3664 var address: linux.sockaddr.in = .{
3665 .port = 0,
3666 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
3667 };
3668 const listener_socket = try createListenerSocket(&address);
3669 defer posix.close(listener_socket);
3670
3671 // submit multishot accept operation
3672 var addr: posix.sockaddr = undefined;
3673 var addr_len: posix.socklen_t = @sizeOf(@TypeOf(addr));
3674 const userdata: u64 = 0xaaaaaaaa;
3675 _ = try ring.accept_multishot(userdata, listener_socket, &addr, &addr_len, 0);
3676 try testing.expectEqual(@as(u32, 1), try ring.submit());
3677
3678 var nr: usize = 4; // number of clients to connect
3679 while (nr > 0) : (nr -= 1) {
3680 // connect client
3681 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3682 errdefer posix.close(client);
3683 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
3684
3685 // test accept completion
3686 var cqe = try ring.copy_cqe();
3687 if (cqe.err() == .INVAL) return error.SkipZigTest;
3688 try testing.expect(cqe.res > 0);
3689 try testing.expect(cqe.user_data == userdata);
3690 try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE > 0); // more flag is set
3691
3692 posix.close(client);
3693 }
3694}
3695
3696test "accept/connect/send_zc/recv" {
3697 try skipKernelLessThan(.{ .major = 6, .minor = 0, .patch = 0 });
3698
3699 var ring = IoUring.init(16, 0) catch |err| switch (err) {
3700 error.SystemOutdated => return error.SkipZigTest,
3701 error.PermissionDenied => return error.SkipZigTest,
3702 else => return err,
3703 };
3704 defer ring.deinit();
3705
3706 const socket_test_harness = try createSocketTestHarness(&ring);
3707 defer socket_test_harness.close();
3708
3709 const buffer_send = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
3710 var buffer_recv = [_]u8{0} ** 10;
3711
3712 // zero-copy send
3713 const sqe_send = try ring.send_zc(0xeeeeeeee, socket_test_harness.client, buffer_send[0..], 0, 0);
3714 sqe_send.flags |= linux.IOSQE_IO_LINK;
3715 _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
3716 try testing.expectEqual(@as(u32, 2), try ring.submit());
3717
3718 var cqe_send = try ring.copy_cqe();
3719 // First completion of zero-copy send.
3720 // IORING_CQE_F_MORE, means that there
3721 // will be a second completion event / notification for the
3722 // request, with the user_data field set to the same value.
3723 // buffer_send must be keep alive until second cqe.
3724 try testing.expectEqual(linux.io_uring_cqe{
3725 .user_data = 0xeeeeeeee,
3726 .res = buffer_send.len,
3727 .flags = linux.IORING_CQE_F_MORE,
3728 }, cqe_send);
3729
3730 cqe_send, const cqe_recv = brk: {
3731 const cqe1 = try ring.copy_cqe();
3732 const cqe2 = try ring.copy_cqe();
3733 break :brk if (cqe1.user_data == 0xeeeeeeee) .{ cqe1, cqe2 } else .{ cqe2, cqe1 };
3734 };
3735
3736 try testing.expectEqual(linux.io_uring_cqe{
3737 .user_data = 0xffffffff,
3738 .res = buffer_recv.len,
3739 .flags = cqe_recv.flags & linux.IORING_CQE_F_SOCK_NONEMPTY,
3740 }, cqe_recv);
3741 try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]);
3742
3743 // Second completion of zero-copy send.
3744 // IORING_CQE_F_NOTIF in flags signals that kernel is done with send_buffer
3745 try testing.expectEqual(linux.io_uring_cqe{
3746 .user_data = 0xeeeeeeee,
3747 .res = 0,
3748 .flags = linux.IORING_CQE_F_NOTIF,
3749 }, cqe_send);
3750}
3751
3752test "accept_direct" {
3753 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
3754
3755 var ring = IoUring.init(1, 0) catch |err| switch (err) {
3756 error.SystemOutdated => return error.SkipZigTest,
3757 error.PermissionDenied => return error.SkipZigTest,
3758 else => return err,
3759 };
3760 defer ring.deinit();
3761 var address: linux.sockaddr.in = .{
3762 .port = 0,
3763 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
3764 };
3765
3766 // register direct file descriptors
3767 var registered_fds = [_]linux.fd_t{-1} ** 2;
3768 try ring.register_files(registered_fds[0..]);
3769
3770 const listener_socket = try createListenerSocket(&address);
3771 defer posix.close(listener_socket);
3772
3773 const accept_userdata: u64 = 0xaaaaaaaa;
3774 const read_userdata: u64 = 0xbbbbbbbb;
3775 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
3776
3777 for (0..2) |_| {
3778 for (registered_fds, 0..) |_, i| {
3779 var buffer_recv = [_]u8{0} ** 16;
3780 const buffer_send: []const u8 = data[0 .. data.len - i]; // make it different at each loop
3781
3782 // submit accept, will chose registered fd and return index in cqe
3783 _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0);
3784 try testing.expectEqual(@as(u32, 1), try ring.submit());
3785
3786 // connect
3787 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3788 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
3789 defer posix.close(client);
3790
3791 // accept completion
3792 const cqe_accept = try ring.copy_cqe();
3793 try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err());
3794 const fd_index = cqe_accept.res;
3795 try testing.expect(fd_index < registered_fds.len);
3796 try testing.expect(cqe_accept.user_data == accept_userdata);
3797
3798 // send data
3799 _ = try posix.send(client, buffer_send, 0);
3800
3801 // Example of how to use registered fd:
3802 // Submit receive to fixed file returned by accept (fd_index).
3803 // Fd field is set to registered file index, returned by accept.
3804 // Flag linux.IOSQE_FIXED_FILE must be set.
3805 const recv_sqe = try ring.recv(read_userdata, fd_index, .{ .buffer = &buffer_recv }, 0);
3806 recv_sqe.flags |= linux.IOSQE_FIXED_FILE;
3807 try testing.expectEqual(@as(u32, 1), try ring.submit());
3808
3809 // accept receive
3810 const recv_cqe = try ring.copy_cqe();
3811 try testing.expect(recv_cqe.user_data == read_userdata);
3812 try testing.expect(recv_cqe.res == buffer_send.len);
3813 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]);
3814 }
3815 // no more available fds, accept will get NFILE error
3816 {
3817 // submit accept
3818 _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0);
3819 try testing.expectEqual(@as(u32, 1), try ring.submit());
3820 // connect
3821 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3822 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
3823 defer posix.close(client);
3824 // completion with error
3825 const cqe_accept = try ring.copy_cqe();
3826 try testing.expect(cqe_accept.user_data == accept_userdata);
3827 try testing.expectEqual(posix.E.NFILE, cqe_accept.err());
3828 }
3829 // return file descriptors to kernel
3830 try ring.register_files_update(0, registered_fds[0..]);
3831 }
3832 try ring.unregister_files();
3833}
3834
3835test "accept_multishot_direct" {
3836 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
3837
3838 if (builtin.cpu.arch == .riscv64) {
3839 // https://github.com/ziglang/zig/issues/25734
3840 return error.SkipZigTest;
3841 }
3842
3843 var ring = IoUring.init(1, 0) catch |err| switch (err) {
3844 error.SystemOutdated => return error.SkipZigTest,
3845 error.PermissionDenied => return error.SkipZigTest,
3846 else => return err,
3847 };
3848 defer ring.deinit();
3849
3850 var address: linux.sockaddr.in = .{
3851 .port = 0,
3852 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
3853 };
3854
3855 var registered_fds = [_]linux.fd_t{-1} ** 2;
3856 try ring.register_files(registered_fds[0..]);
3857
3858 const listener_socket = try createListenerSocket(&address);
3859 defer posix.close(listener_socket);
3860
3861 const accept_userdata: u64 = 0xaaaaaaaa;
3862
3863 for (0..2) |_| {
3864 // submit multishot accept
3865 // Will chose registered fd and return index of the selected registered file in cqe.
3866 _ = try ring.accept_multishot_direct(accept_userdata, listener_socket, null, null, 0);
3867 try testing.expectEqual(@as(u32, 1), try ring.submit());
3868
3869 for (registered_fds) |_| {
3870 // connect
3871 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3872 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
3873 defer posix.close(client);
3874
3875 // accept completion
3876 const cqe_accept = try ring.copy_cqe();
3877 const fd_index = cqe_accept.res;
3878 try testing.expect(fd_index < registered_fds.len);
3879 try testing.expect(cqe_accept.user_data == accept_userdata);
3880 try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE > 0); // has more is set
3881 }
3882 // No more available fds, accept will get NFILE error.
3883 // Multishot is terminated (more flag is not set).
3884 {
3885 // connect
3886 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3887 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
3888 defer posix.close(client);
3889 // completion with error
3890 const cqe_accept = try ring.copy_cqe();
3891 try testing.expect(cqe_accept.user_data == accept_userdata);
3892 try testing.expectEqual(posix.E.NFILE, cqe_accept.err());
3893 try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE == 0); // has more is not set
3894 }
3895 // return file descriptors to kernel
3896 try ring.register_files_update(0, registered_fds[0..]);
3897 }
3898 try ring.unregister_files();
3899}
3900
3901test "socket" {
3902 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
3903
3904 var ring = IoUring.init(1, 0) catch |err| switch (err) {
3905 error.SystemOutdated => return error.SkipZigTest,
3906 error.PermissionDenied => return error.SkipZigTest,
3907 else => return err,
3908 };
3909 defer ring.deinit();
3910
3911 // prepare, submit socket operation
3912 _ = try ring.socket(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0);
3913 try testing.expectEqual(@as(u32, 1), try ring.submit());
3914
3915 // test completion
3916 var cqe = try ring.copy_cqe();
3917 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
3918 const fd: linux.fd_t = @intCast(cqe.res);
3919 try testing.expect(fd > 2);
3920
3921 posix.close(fd);
3922}
3923
3924test "socket_direct/socket_direct_alloc/close_direct" {
3925 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
3926
3927 var ring = IoUring.init(2, 0) catch |err| switch (err) {
3928 error.SystemOutdated => return error.SkipZigTest,
3929 error.PermissionDenied => return error.SkipZigTest,
3930 else => return err,
3931 };
3932 defer ring.deinit();
3933
3934 var registered_fds = [_]linux.fd_t{-1} ** 3;
3935 try ring.register_files(registered_fds[0..]);
3936
3937 // create socket in registered file descriptor at index 0 (last param)
3938 _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 0);
3939 try testing.expectEqual(@as(u32, 1), try ring.submit());
3940 var cqe_socket = try ring.copy_cqe();
3941 try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err());
3942 try testing.expect(cqe_socket.res == 0);
3943
3944 // create socket in registered file descriptor at index 1 (last param)
3945 _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 1);
3946 try testing.expectEqual(@as(u32, 1), try ring.submit());
3947 cqe_socket = try ring.copy_cqe();
3948 try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err());
3949 try testing.expect(cqe_socket.res == 0); // res is 0 when index is specified
3950
3951 // create socket in kernel chosen file descriptor index (_alloc version)
3952 // completion res has index from registered files
3953 _ = try ring.socket_direct_alloc(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0);
3954 try testing.expectEqual(@as(u32, 1), try ring.submit());
3955 cqe_socket = try ring.copy_cqe();
3956 try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err());
3957 try testing.expect(cqe_socket.res == 2); // returns registered file index
3958
3959 // use sockets from registered_fds in connect operation
3960 var address: linux.sockaddr.in = .{
3961 .port = 0,
3962 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
3963 };
3964 const listener_socket = try createListenerSocket(&address);
3965 defer posix.close(listener_socket);
3966 const accept_userdata: u64 = 0xaaaaaaaa;
3967 const connect_userdata: u64 = 0xbbbbbbbb;
3968 const close_userdata: u64 = 0xcccccccc;
3969 for (registered_fds, 0..) |_, fd_index| {
3970 // prepare accept
3971 _ = try ring.accept(accept_userdata, listener_socket, null, null, 0);
3972 // prepare connect with fixed socket
3973 const connect_sqe = try ring.connect(connect_userdata, @intCast(fd_index), addrAny(&address), @sizeOf(linux.sockaddr.in));
3974 connect_sqe.flags |= linux.IOSQE_FIXED_FILE; // fd is fixed file index
3975 // submit both
3976 try testing.expectEqual(@as(u32, 2), try ring.submit());
3977 // get completions
3978 var cqe_connect = try ring.copy_cqe();
3979 var cqe_accept = try ring.copy_cqe();
3980 // ignore order
3981 if (cqe_connect.user_data == accept_userdata and cqe_accept.user_data == connect_userdata) {
3982 const a = cqe_accept;
3983 const b = cqe_connect;
3984 cqe_accept = b;
3985 cqe_connect = a;
3986 }
3987 // test connect completion
3988 try testing.expect(cqe_connect.user_data == connect_userdata);
3989 try testing.expectEqual(posix.E.SUCCESS, cqe_connect.err());
3990 // test accept completion
3991 try testing.expect(cqe_accept.user_data == accept_userdata);
3992 try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err());
3993
3994 // submit and test close_direct
3995 _ = try ring.close_direct(close_userdata, @intCast(fd_index));
3996 try testing.expectEqual(@as(u32, 1), try ring.submit());
3997 var cqe_close = try ring.copy_cqe();
3998 try testing.expect(cqe_close.user_data == close_userdata);
3999 try testing.expectEqual(posix.E.SUCCESS, cqe_close.err());
4000 }
4001
4002 try ring.unregister_files();
4003}
4004
4005test "openat_direct/close_direct" {
4006 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
4007
4008 var ring = IoUring.init(2, 0) catch |err| switch (err) {
4009 error.SystemOutdated => return error.SkipZigTest,
4010 error.PermissionDenied => return error.SkipZigTest,
4011 else => return err,
4012 };
4013 defer ring.deinit();
4014
4015 var registered_fds = [_]linux.fd_t{-1} ** 3;
4016 try ring.register_files(registered_fds[0..]);
4017
4018 var tmp = std.testing.tmpDir(.{});
4019 defer tmp.cleanup();
4020 const path = "test_io_uring_close_direct";
4021 const flags: linux.O = .{ .ACCMODE = .RDWR, .CREAT = true };
4022 const mode: posix.mode_t = 0o666;
4023 const user_data: u64 = 0;
4024
4025 // use registered file at index 0 (last param)
4026 _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, 0);
4027 try testing.expectEqual(@as(u32, 1), try ring.submit());
4028 var cqe = try ring.copy_cqe();
4029 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4030 try testing.expect(cqe.res == 0);
4031
4032 // use registered file at index 1
4033 _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, 1);
4034 try testing.expectEqual(@as(u32, 1), try ring.submit());
4035 cqe = try ring.copy_cqe();
4036 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4037 try testing.expect(cqe.res == 0); // res is 0 when we specify index
4038
4039 // let kernel choose registered file index
4040 _ = try ring.openat_direct(user_data, tmp.dir.fd, path, flags, mode, linux.IORING_FILE_INDEX_ALLOC);
4041 try testing.expectEqual(@as(u32, 1), try ring.submit());
4042 cqe = try ring.copy_cqe();
4043 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4044 try testing.expect(cqe.res == 2); // chosen index is in res
4045
4046 // close all open file descriptors
4047 for (registered_fds, 0..) |_, fd_index| {
4048 _ = try ring.close_direct(user_data, @intCast(fd_index));
4049 try testing.expectEqual(@as(u32, 1), try ring.submit());
4050 var cqe_close = try ring.copy_cqe();
4051 try testing.expectEqual(posix.E.SUCCESS, cqe_close.err());
4052 }
4053 try ring.unregister_files();
4054}
4055
4056test "waitid" {
4057 try skipKernelLessThan(.{ .major = 6, .minor = 7, .patch = 0 });
4058
4059 var ring = IoUring.init(16, 0) catch |err| switch (err) {
4060 error.SystemOutdated => return error.SkipZigTest,
4061 error.PermissionDenied => return error.SkipZigTest,
4062 else => return err,
4063 };
4064 defer ring.deinit();
4065
4066 const pid = try posix.fork();
4067 if (pid == 0) {
4068 posix.exit(7);
4069 }
4070
4071 var siginfo: posix.siginfo_t = undefined;
4072 _ = try ring.waitid(0, .PID, pid, &siginfo, posix.W.EXITED, 0);
4073
4074 try testing.expectEqual(1, try ring.submit());
4075
4076 const cqe_waitid = try ring.copy_cqe();
4077 try testing.expectEqual(0, cqe_waitid.res);
4078 try testing.expectEqual(pid, siginfo.fields.common.first.piduid.pid);
4079 try testing.expectEqual(7, siginfo.fields.common.second.sigchld.status);
4080}
4081
4082/// For use in tests. Returns SkipZigTest if kernel version is less than required.
4083inline fn skipKernelLessThan(required: std.SemanticVersion) !void {
4084 if (!is_linux) return error.SkipZigTest;
4085
4086 var uts: linux.utsname = undefined;
4087 const res = linux.uname(&uts);
4088 switch (linux.errno(res)) {
4089 .SUCCESS => {},
4090 else => |errno| return posix.unexpectedErrno(errno),
4091 }
4092
4093 const release = mem.sliceTo(&uts.release, 0);
4094 // Strips potential extra, as kernel version might not be semver compliant, example "6.8.9-300.fc40.x86_64"
4095 const extra_index = std.mem.findAny(u8, release, "-+");
4096 const stripped = release[0..(extra_index orelse release.len)];
4097 // Make sure the input don't rely on the extra we just stripped
4098 try testing.expect(required.pre == null and required.build == null);
4099
4100 var current = try std.SemanticVersion.parse(stripped);
4101 current.pre = null; // don't check pre field
4102 if (required.order(current) == .gt) return error.SkipZigTest;
4103}
4104
4105test BufferGroup {
4106 if (!is_linux) return error.SkipZigTest;
4107
4108 // Init IoUring
4109 var ring = IoUring.init(16, 0) catch |err| switch (err) {
4110 error.SystemOutdated => return error.SkipZigTest,
4111 error.PermissionDenied => return error.SkipZigTest,
4112 else => return err,
4113 };
4114 defer ring.deinit();
4115
4116 // Init buffer group for ring
4117 const group_id: u16 = 1; // buffers group id
4118 const buffers_count: u16 = 1; // number of buffers in buffer group
4119 const buffer_size: usize = 128; // size of each buffer in group
4120 var buf_grp = BufferGroup.init(
4121 &ring,
4122 testing.allocator,
4123 group_id,
4124 buffer_size,
4125 buffers_count,
4126 ) catch |err| switch (err) {
4127 // kernel older than 5.19
4128 error.ArgumentsInvalid => return error.SkipZigTest,
4129 else => return err,
4130 };
4131 defer buf_grp.deinit(testing.allocator);
4132
4133 // Create client/server fds
4134 const fds = try createSocketTestHarness(&ring);
4135 defer fds.close();
4136 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
4137
4138 // Client sends data
4139 {
4140 _ = try ring.send(1, fds.client, data[0..], 0);
4141 const submitted = try ring.submit();
4142 try testing.expectEqual(1, submitted);
4143 const cqe_send = try ring.copy_cqe();
4144 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
4145 try testing.expectEqual(linux.io_uring_cqe{ .user_data = 1, .res = data.len, .flags = 0 }, cqe_send);
4146 }
4147
4148 // Server uses buffer group receive
4149 {
4150 // Submit recv operation, buffer will be chosen from buffer group
4151 _ = try buf_grp.recv(2, fds.server, 0);
4152 const submitted = try ring.submit();
4153 try testing.expectEqual(1, submitted);
4154
4155 // ... when we have completion for recv operation
4156 const cqe = try ring.copy_cqe();
4157 try testing.expectEqual(2, cqe.user_data); // matches submitted user_data
4158 try testing.expect(cqe.res >= 0); // success
4159 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4160 try testing.expectEqual(data.len, @as(usize, @intCast(cqe.res))); // cqe.res holds received data len
4161
4162 // Get buffer from pool
4163 const buf = try buf_grp.get(cqe);
4164 try testing.expectEqualSlices(u8, &data, buf);
4165 // Release buffer to the kernel when application is done with it
4166 try buf_grp.put(cqe);
4167 }
4168}
4169
4170test "ring mapped buffers recv" {
4171 if (!is_linux) return error.SkipZigTest;
4172
4173 var ring = IoUring.init(16, 0) catch |err| switch (err) {
4174 error.SystemOutdated => return error.SkipZigTest,
4175 error.PermissionDenied => return error.SkipZigTest,
4176 else => return err,
4177 };
4178 defer ring.deinit();
4179
4180 // init buffer group
4181 const group_id: u16 = 1; // buffers group id
4182 const buffers_count: u16 = 2; // number of buffers in buffer group
4183 const buffer_size: usize = 4; // size of each buffer in group
4184 var buf_grp = BufferGroup.init(
4185 &ring,
4186 testing.allocator,
4187 group_id,
4188 buffer_size,
4189 buffers_count,
4190 ) catch |err| switch (err) {
4191 // kernel older than 5.19
4192 error.ArgumentsInvalid => return error.SkipZigTest,
4193 else => return err,
4194 };
4195 defer buf_grp.deinit(testing.allocator);
4196
4197 // create client/server fds
4198 const fds = try createSocketTestHarness(&ring);
4199 defer fds.close();
4200
4201 // for random user_data in sqe/cqe
4202 var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed);
4203 var rnd = Rnd.random();
4204
4205 var round: usize = 4; // repeat send/recv cycle round times
4206 while (round > 0) : (round -= 1) {
4207 // client sends data
4208 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
4209 {
4210 const user_data = rnd.int(u64);
4211 _ = try ring.send(user_data, fds.client, data[0..], 0);
4212 try testing.expectEqual(@as(u32, 1), try ring.submit());
4213 const cqe_send = try ring.copy_cqe();
4214 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
4215 try testing.expectEqual(linux.io_uring_cqe{ .user_data = user_data, .res = data.len, .flags = 0 }, cqe_send);
4216 }
4217 var pos: usize = 0;
4218
4219 // read first chunk
4220 const cqe1 = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64));
4221 var buf = try buf_grp.get(cqe1);
4222 try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf);
4223 pos += buf.len;
4224 // second chunk
4225 const cqe2 = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64));
4226 buf = try buf_grp.get(cqe2);
4227 try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf);
4228 pos += buf.len;
4229
4230 // both buffers provided to the kernel are used so we get error
4231 // 'no more buffers', until we put buffers to the kernel
4232 {
4233 const user_data = rnd.int(u64);
4234 _ = try buf_grp.recv(user_data, fds.server, 0);
4235 try testing.expectEqual(@as(u32, 1), try ring.submit());
4236 const cqe = try ring.copy_cqe();
4237 try testing.expectEqual(user_data, cqe.user_data);
4238 try testing.expect(cqe.res < 0); // fail
4239 try testing.expectEqual(posix.E.NOBUFS, cqe.err());
4240 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only
4241 try testing.expectError(error.NoBufferSelected, cqe.buffer_id());
4242 }
4243
4244 // put buffers back to the kernel
4245 try buf_grp.put(cqe1);
4246 try buf_grp.put(cqe2);
4247
4248 // read remaining data
4249 while (pos < data.len) {
4250 const cqe = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64));
4251 buf = try buf_grp.get(cqe);
4252 try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf);
4253 pos += buf.len;
4254 try buf_grp.put(cqe);
4255 }
4256 }
4257}
4258
4259test "ring mapped buffers multishot recv" {
4260 if (!is_linux) return error.SkipZigTest;
4261
4262 var ring = IoUring.init(16, 0) catch |err| switch (err) {
4263 error.SystemOutdated => return error.SkipZigTest,
4264 error.PermissionDenied => return error.SkipZigTest,
4265 else => return err,
4266 };
4267 defer ring.deinit();
4268
4269 // init buffer group
4270 const group_id: u16 = 1; // buffers group id
4271 const buffers_count: u16 = 2; // number of buffers in buffer group
4272 const buffer_size: usize = 4; // size of each buffer in group
4273 var buf_grp = BufferGroup.init(
4274 &ring,
4275 testing.allocator,
4276 group_id,
4277 buffer_size,
4278 buffers_count,
4279 ) catch |err| switch (err) {
4280 // kernel older than 5.19
4281 error.ArgumentsInvalid => return error.SkipZigTest,
4282 else => return err,
4283 };
4284 defer buf_grp.deinit(testing.allocator);
4285
4286 // create client/server fds
4287 const fds = try createSocketTestHarness(&ring);
4288 defer fds.close();
4289
4290 // for random user_data in sqe/cqe
4291 var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed);
4292 var rnd = Rnd.random();
4293
4294 var round: usize = 4; // repeat send/recv cycle round times
4295 while (round > 0) : (round -= 1) {
4296 // client sends data
4297 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
4298 {
4299 const user_data = rnd.int(u64);
4300 _ = try ring.send(user_data, fds.client, data[0..], 0);
4301 try testing.expectEqual(@as(u32, 1), try ring.submit());
4302 const cqe_send = try ring.copy_cqe();
4303 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
4304 try testing.expectEqual(linux.io_uring_cqe{ .user_data = user_data, .res = data.len, .flags = 0 }, cqe_send);
4305 }
4306
4307 // start multishot recv
4308 var recv_user_data = rnd.int(u64);
4309 _ = try buf_grp.recv_multishot(recv_user_data, fds.server, 0);
4310 try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit
4311
4312 // server reads data into provided buffers
4313 // there are 2 buffers of size 4, so each read gets only chunk of data
4314 // we read four chunks of 4, 4, 4, 4 bytes each
4315 var chunk: []const u8 = data[0..buffer_size]; // first chunk
4316 const cqe1 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
4317 try testing.expect(cqe1.flags & linux.IORING_CQE_F_MORE > 0);
4318
4319 chunk = data[buffer_size .. buffer_size * 2]; // second chunk
4320 const cqe2 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
4321 try testing.expect(cqe2.flags & linux.IORING_CQE_F_MORE > 0);
4322
4323 // both buffers provided to the kernel are used so we get error
4324 // 'no more buffers', until we put buffers to the kernel
4325 {
4326 const cqe = try ring.copy_cqe();
4327 try testing.expectEqual(recv_user_data, cqe.user_data);
4328 try testing.expect(cqe.res < 0); // fail
4329 try testing.expectEqual(posix.E.NOBUFS, cqe.err());
4330 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only
4331 // has more is not set
4332 // indicates that multishot is finished
4333 try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE == 0);
4334 try testing.expectError(error.NoBufferSelected, cqe.buffer_id());
4335 }
4336
4337 // put buffers back to the kernel
4338 try buf_grp.put(cqe1);
4339 try buf_grp.put(cqe2);
4340
4341 // restart multishot
4342 recv_user_data = rnd.int(u64);
4343 _ = try buf_grp.recv_multishot(recv_user_data, fds.server, 0);
4344 try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit
4345
4346 chunk = data[buffer_size * 2 .. buffer_size * 3]; // third chunk
4347 const cqe3 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
4348 try testing.expect(cqe3.flags & linux.IORING_CQE_F_MORE > 0);
4349 try buf_grp.put(cqe3);
4350
4351 chunk = data[buffer_size * 3 ..]; // last chunk
4352 const cqe4 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
4353 try testing.expect(cqe4.flags & linux.IORING_CQE_F_MORE > 0);
4354 try buf_grp.put(cqe4);
4355
4356 // cancel pending multishot recv operation
4357 {
4358 const cancel_user_data = rnd.int(u64);
4359 _ = try ring.cancel(cancel_user_data, recv_user_data, 0);
4360 try testing.expectEqual(@as(u32, 1), try ring.submit());
4361
4362 // expect completion of cancel operation and completion of recv operation
4363 var cqe_cancel = try ring.copy_cqe();
4364 if (cqe_cancel.err() == .INVAL) return error.SkipZigTest;
4365 var cqe_recv = try ring.copy_cqe();
4366 if (cqe_recv.err() == .INVAL) return error.SkipZigTest;
4367
4368 // don't depend on order of completions
4369 if (cqe_cancel.user_data == recv_user_data and cqe_recv.user_data == cancel_user_data) {
4370 const a = cqe_cancel;
4371 const b = cqe_recv;
4372 cqe_cancel = b;
4373 cqe_recv = a;
4374 }
4375
4376 // Note on different kernel results:
4377 // on older kernel (tested with v6.0.16, v6.1.57, v6.2.12, v6.4.16)
4378 // cqe_cancel.err() == .NOENT
4379 // cqe_recv.err() == .NOBUFS
4380 // on kernel (tested with v6.5.0, v6.5.7)
4381 // cqe_cancel.err() == .SUCCESS
4382 // cqe_recv.err() == .CANCELED
4383 // Upstream reference: https://github.com/axboe/liburing/issues/984
4384
4385 // cancel operation is success (or NOENT on older kernels)
4386 try testing.expectEqual(cancel_user_data, cqe_cancel.user_data);
4387 try testing.expect(cqe_cancel.err() == .NOENT or cqe_cancel.err() == .SUCCESS);
4388
4389 // recv operation is failed with err CANCELED (or NOBUFS on older kernels)
4390 try testing.expectEqual(recv_user_data, cqe_recv.user_data);
4391 try testing.expect(cqe_recv.res < 0);
4392 try testing.expect(cqe_recv.err() == .NOBUFS or cqe_recv.err() == .CANCELED);
4393 try testing.expect(cqe_recv.flags & linux.IORING_CQE_F_MORE == 0);
4394 }
4395 }
4396}
4397
4398// Prepare, submit recv and get cqe using buffer group.
4399fn buf_grp_recv_submit_get_cqe(
4400 ring: *IoUring,
4401 buf_grp: *BufferGroup,
4402 fd: linux.fd_t,
4403 user_data: u64,
4404) !linux.io_uring_cqe {
4405 // prepare and submit recv
4406 const sqe = try buf_grp.recv(user_data, fd, 0);
4407 try testing.expect(sqe.flags & linux.IOSQE_BUFFER_SELECT == linux.IOSQE_BUFFER_SELECT);
4408 try testing.expect(sqe.buf_index == buf_grp.group_id);
4409 try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit
4410 // get cqe, expect success
4411 const cqe = try ring.copy_cqe();
4412 try testing.expectEqual(user_data, cqe.user_data);
4413 try testing.expect(cqe.res >= 0); // success
4414 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4415 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set
4416
4417 return cqe;
4418}
4419
4420fn expect_buf_grp_cqe(
4421 ring: *IoUring,
4422 buf_grp: *BufferGroup,
4423 user_data: u64,
4424 expected: []const u8,
4425) !linux.io_uring_cqe {
4426 // get cqe
4427 const cqe = try ring.copy_cqe();
4428 try testing.expectEqual(user_data, cqe.user_data);
4429 try testing.expect(cqe.res >= 0); // success
4430 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set
4431 try testing.expectEqual(expected.len, @as(usize, @intCast(cqe.res)));
4432 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4433
4434 // get buffer from pool
4435 const buffer_id = try cqe.buffer_id();
4436 const len = @as(usize, @intCast(cqe.res));
4437 const buf = buf_grp.get_by_id(buffer_id)[0..len];
4438 try testing.expectEqualSlices(u8, expected, buf);
4439
4440 return cqe;
4441}
4442
4443test "copy_cqes with wrapping sq.cqes buffer" {
4444 if (!is_linux) return error.SkipZigTest;
4445
4446 var ring = IoUring.init(2, 0) catch |err| switch (err) {
4447 error.SystemOutdated => return error.SkipZigTest,
4448 error.PermissionDenied => return error.SkipZigTest,
4449 else => return err,
4450 };
4451 defer ring.deinit();
4452
4453 try testing.expectEqual(2, ring.sq.sqes.len);
4454 try testing.expectEqual(4, ring.cq.cqes.len);
4455
4456 // submit 2 entries, receive 2 completions
4457 var cqes: [8]linux.io_uring_cqe = undefined;
4458 {
4459 for (0..2) |_| {
4460 const sqe = try ring.get_sqe();
4461 sqe.prep_timeout(&.{ .sec = 0, .nsec = 10000 }, 0, 0);
4462 try testing.expect(try ring.submit() == 1);
4463 }
4464 var cqe_count: u32 = 0;
4465 while (cqe_count < 2) {
4466 cqe_count += try ring.copy_cqes(&cqes, 2 - cqe_count);
4467 }
4468 }
4469
4470 try testing.expectEqual(2, ring.cq.head.*);
4471
4472 // sq.sqes len is 4, starting at position 2
4473 // every 4 entries submit wraps completion buffer
4474 // we are reading ring.cq.cqes at indexes 2,3,0,1
4475 for (1..1024) |i| {
4476 for (0..4) |_| {
4477 const sqe = try ring.get_sqe();
4478 sqe.prep_timeout(&.{ .sec = 0, .nsec = 10000 }, 0, 0);
4479 try testing.expect(try ring.submit() == 1);
4480 }
4481 var cqe_count: u32 = 0;
4482 while (cqe_count < 4) {
4483 cqe_count += try ring.copy_cqes(&cqes, 4 - cqe_count);
4484 }
4485 try testing.expectEqual(4, cqe_count);
4486 try testing.expectEqual(2 + 4 * i, ring.cq.head.*);
4487 }
4488}
4489
4490test "bind/listen/connect" {
4491 if (builtin.cpu.arch == .s390x) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25956
4492
4493 var ring = IoUring.init(4, 0) catch |err| switch (err) {
4494 error.SystemOutdated => return error.SkipZigTest,
4495 error.PermissionDenied => return error.SkipZigTest,
4496 else => return err,
4497 };
4498 defer ring.deinit();
4499
4500 const probe = ring.get_probe() catch return error.SkipZigTest;
4501 // LISTEN is higher required operation
4502 if (!probe.is_supported(.LISTEN)) return error.SkipZigTest;
4503
4504 var addr: linux.sockaddr.in = .{
4505 .port = 0,
4506 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
4507 };
4508 const proto: u32 = if (addr.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP;
4509
4510 const listen_fd = brk: {
4511 // Create socket
4512 _ = try ring.socket(1, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
4513 try testing.expectEqual(1, try ring.submit());
4514 var cqe = try ring.copy_cqe();
4515 try testing.expectEqual(1, cqe.user_data);
4516 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4517 const listen_fd: linux.fd_t = @intCast(cqe.res);
4518 try testing.expect(listen_fd > 2);
4519
4520 // Prepare: set socket option * 2, bind, listen
4521 var optval: u32 = 1;
4522 (try ring.setsockopt(2, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval))).link_next();
4523 (try ring.setsockopt(3, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEPORT, mem.asBytes(&optval))).link_next();
4524 (try ring.bind(4, listen_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in), 0)).link_next();
4525 _ = try ring.listen(5, listen_fd, 1, 0);
4526 // Submit 4 operations
4527 try testing.expectEqual(4, try ring.submit());
4528 // Expect all to succeed
4529 for (2..6) |user_data| {
4530 cqe = try ring.copy_cqe();
4531 try testing.expectEqual(user_data, cqe.user_data);
4532 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4533 }
4534
4535 // Check that socket option is set
4536 optval = 0;
4537 _ = try ring.getsockopt(5, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval));
4538 try testing.expectEqual(1, try ring.submit());
4539 cqe = try ring.copy_cqe();
4540 try testing.expectEqual(5, cqe.user_data);
4541 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4542 try testing.expectEqual(1, optval);
4543
4544 // Read system assigned port into addr
4545 var addr_len: posix.socklen_t = @sizeOf(linux.sockaddr.in);
4546 try posix.getsockname(listen_fd, addrAny(&addr), &addr_len);
4547
4548 break :brk listen_fd;
4549 };
4550
4551 const connect_fd = brk: {
4552 // Create connect socket
4553 _ = try ring.socket(6, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
4554 try testing.expectEqual(1, try ring.submit());
4555 const cqe = try ring.copy_cqe();
4556 try testing.expectEqual(6, cqe.user_data);
4557 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4558 // Get connect socket fd
4559 const connect_fd: linux.fd_t = @intCast(cqe.res);
4560 try testing.expect(connect_fd > 2 and connect_fd != listen_fd);
4561 break :brk connect_fd;
4562 };
4563
4564 // Prepare accept/connect operations
4565 _ = try ring.accept(7, listen_fd, null, null, 0);
4566 _ = try ring.connect(8, connect_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in));
4567 try testing.expectEqual(2, try ring.submit());
4568 // Get listener accepted socket
4569 var accept_fd: posix.socket_t = 0;
4570 for (0..2) |_| {
4571 const cqe = try ring.copy_cqe();
4572 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4573 if (cqe.user_data == 7) {
4574 accept_fd = @intCast(cqe.res);
4575 } else {
4576 try testing.expectEqual(8, cqe.user_data);
4577 }
4578 }
4579 try testing.expect(accept_fd > 2 and accept_fd != listen_fd and accept_fd != connect_fd);
4580
4581 // Communicate
4582 try testSendRecv(&ring, connect_fd, accept_fd);
4583 try testSendRecv(&ring, accept_fd, connect_fd);
4584
4585 // Shutdown and close all sockets
4586 for ([_]posix.socket_t{ connect_fd, accept_fd, listen_fd }) |fd| {
4587 (try ring.shutdown(9, fd, posix.SHUT.RDWR)).link_next();
4588 _ = try ring.close(10, fd);
4589 try testing.expectEqual(2, try ring.submit());
4590 for (0..2) |i| {
4591 const cqe = try ring.copy_cqe();
4592 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4593 try testing.expectEqual(9 + i, cqe.user_data);
4594 }
4595 }
4596}
4597
4598fn testSendRecv(ring: *IoUring, send_fd: posix.socket_t, recv_fd: posix.socket_t) !void {
4599 const buffer_send = "0123456789abcdf" ** 10;
4600 var buffer_recv: [buffer_send.len * 2]u8 = undefined;
4601
4602 // 2 sends
4603 _ = try ring.send(1, send_fd, buffer_send, linux.MSG.WAITALL);
4604 _ = try ring.send(2, send_fd, buffer_send, linux.MSG.WAITALL);
4605 try testing.expectEqual(2, try ring.submit());
4606 for (0..2) |i| {
4607 const cqe = try ring.copy_cqe();
4608 try testing.expectEqual(1 + i, cqe.user_data);
4609 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4610 try testing.expectEqual(buffer_send.len, @as(usize, @intCast(cqe.res)));
4611 }
4612
4613 // receive
4614 var recv_len: usize = 0;
4615 while (recv_len < buffer_send.len * 2) {
4616 _ = try ring.recv(3, recv_fd, .{ .buffer = buffer_recv[recv_len..] }, 0);
4617 try testing.expectEqual(1, try ring.submit());
4618 const cqe = try ring.copy_cqe();
4619 try testing.expectEqual(3, cqe.user_data);
4620 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4621 recv_len += @intCast(cqe.res);
4622 }
4623
4624 // inspect recv buffer
4625 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]);
4626 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[buffer_send.len..]);
46271920}
46281921
4629fn addrAny(addr: *linux.sockaddr.in) *linux.sockaddr {
4630 return @ptrCast(addr);
1922test {
1923 if (is_linux) _ = @import("IoUring/test.zig");
46311924}
lib/std/os/linux/IoUring/test.zig created+2691
......@@ -0,0 +1,2691 @@
1const builtin = @import("builtin");
2
3const std = @import("../../../std.zig");
4const Io = std.Io;
5const mem = std.mem;
6const assert = std.debug.assert;
7const testing = std.testing;
8const linux = std.os.linux;
9
10const IoUring = std.os.linux.IoUring;
11const BufferGroup = IoUring.BufferGroup;
12
13const posix = std.posix;
14const iovec = posix.iovec;
15const iovec_const = posix.iovec_const;
16
17comptime {
18 assert(builtin.os.tag == .linux);
19}
20
21test "structs/offsets/entries" {
22 try testing.expectEqual(@as(usize, 120), @sizeOf(linux.io_uring_params));
23 try testing.expectEqual(@as(usize, 64), @sizeOf(linux.io_uring_sqe));
24 try testing.expectEqual(@as(usize, 16), @sizeOf(linux.io_uring_cqe));
25
26 try testing.expectEqual(0, linux.IORING_OFF_SQ_RING);
27 try testing.expectEqual(0x8000000, linux.IORING_OFF_CQ_RING);
28 try testing.expectEqual(0x10000000, linux.IORING_OFF_SQES);
29
30 try testing.expectError(error.EntriesZero, IoUring.init(0, 0));
31 try testing.expectError(error.EntriesNotPowerOfTwo, IoUring.init(3, 0));
32}
33
34test "nop" {
35 var ring = IoUring.init(1, 0) catch |err| switch (err) {
36 error.SystemOutdated => return error.SkipZigTest,
37 error.PermissionDenied => return error.SkipZigTest,
38 else => return err,
39 };
40 defer {
41 ring.deinit();
42 testing.expectEqual(@as(linux.fd_t, -1), ring.fd) catch @panic("test failed");
43 }
44
45 const sqe = try ring.nop(0xaaaaaaaa);
46 try testing.expectEqual(linux.io_uring_sqe{
47 .opcode = .NOP,
48 .flags = 0,
49 .ioprio = 0,
50 .fd = 0,
51 .off = 0,
52 .addr = 0,
53 .len = 0,
54 .rw_flags = 0,
55 .user_data = 0xaaaaaaaa,
56 .buf_index = 0,
57 .personality = 0,
58 .splice_fd_in = 0,
59 .addr3 = 0,
60 .resv = 0,
61 }, sqe.*);
62
63 try testing.expectEqual(@as(u32, 0), ring.sq.sqe_head);
64 try testing.expectEqual(@as(u32, 1), ring.sq.sqe_tail);
65 try testing.expectEqual(@as(u32, 0), ring.sq.tail.*);
66 try testing.expectEqual(@as(u32, 0), ring.cq.head.*);
67 try testing.expectEqual(@as(u32, 1), ring.sq_ready());
68 try testing.expectEqual(@as(u32, 0), ring.cq_ready());
69
70 try testing.expectEqual(@as(u32, 1), try ring.submit());
71 try testing.expectEqual(@as(u32, 1), ring.sq.sqe_head);
72 try testing.expectEqual(@as(u32, 1), ring.sq.sqe_tail);
73 try testing.expectEqual(@as(u32, 1), ring.sq.tail.*);
74 try testing.expectEqual(@as(u32, 0), ring.cq.head.*);
75 try testing.expectEqual(@as(u32, 0), ring.sq_ready());
76
77 try testing.expectEqual(linux.io_uring_cqe{
78 .user_data = 0xaaaaaaaa,
79 .res = 0,
80 .flags = 0,
81 }, try ring.copy_cqe());
82 try testing.expectEqual(@as(u32, 1), ring.cq.head.*);
83 try testing.expectEqual(@as(u32, 0), ring.cq_ready());
84
85 const sqe_barrier = try ring.nop(0xbbbbbbbb);
86 sqe_barrier.flags |= linux.IOSQE_IO_DRAIN;
87 try testing.expectEqual(@as(u32, 1), try ring.submit());
88 try testing.expectEqual(linux.io_uring_cqe{
89 .user_data = 0xbbbbbbbb,
90 .res = 0,
91 .flags = 0,
92 }, try ring.copy_cqe());
93 try testing.expectEqual(@as(u32, 2), ring.sq.sqe_head);
94 try testing.expectEqual(@as(u32, 2), ring.sq.sqe_tail);
95 try testing.expectEqual(@as(u32, 2), ring.sq.tail.*);
96 try testing.expectEqual(@as(u32, 2), ring.cq.head.*);
97}
98
99test "readv" {
100 const io = testing.io;
101
102 var ring = IoUring.init(1, 0) catch |err| switch (err) {
103 error.SystemOutdated => return error.SkipZigTest,
104 error.PermissionDenied => return error.SkipZigTest,
105 else => return err,
106 };
107 defer ring.deinit();
108
109 const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{});
110 defer file.close(io);
111
112 // Linux Kernel 5.4 supports IORING_REGISTER_FILES but not sparse fd sets (i.e. an fd of -1).
113 // Linux Kernel 5.5 adds support for sparse fd sets.
114 // Compare:
115 // https://github.com/torvalds/linux/blob/v5.4/fs/io_uring.c#L3119-L3124 vs
116 // https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L6687-L6691
117 // We therefore avoid stressing sparse fd sets here:
118 var registered_fds = [_]linux.fd_t{0} ** 1;
119 const fd_index = 0;
120 registered_fds[fd_index] = file.handle;
121 try ring.register_files(registered_fds[0..]);
122
123 var buffer = [_]u8{42} ** 128;
124 var iovecs = [_]iovec{iovec{ .base = &buffer, .len = buffer.len }};
125 const sqe = try ring.read(0xcccccccc, fd_index, .{ .iovecs = iovecs[0..] }, 0);
126 try testing.expectEqual(linux.IORING_OP.READV, sqe.opcode);
127 sqe.flags |= linux.IOSQE_FIXED_FILE;
128
129 try testing.expectError(error.SubmissionQueueFull, ring.nop(0));
130 try testing.expectEqual(@as(u32, 1), try ring.submit());
131 try testing.expectEqual(linux.io_uring_cqe{
132 .user_data = 0xcccccccc,
133 .res = buffer.len,
134 .flags = 0,
135 }, try ring.copy_cqe());
136 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]);
137
138 try ring.unregister_files();
139}
140
141test "writev/fsync/readv" {
142 const io = testing.io;
143
144 var ring = IoUring.init(4, 0) catch |err| switch (err) {
145 error.SystemOutdated => return error.SkipZigTest,
146 error.PermissionDenied => return error.SkipZigTest,
147 else => return err,
148 };
149 defer ring.deinit();
150
151 var tmp = std.testing.tmpDir(.{});
152 defer tmp.cleanup();
153
154 const path = "test_io_uring_writev_fsync_readv";
155 const file = try tmp.dir.createFile(io, path, .{ .read = true });
156 defer file.close(io);
157 const fd = file.handle;
158
159 const buffer_write = [_]u8{42} ** 128;
160 const iovecs_write = [_]iovec_const{
161 iovec_const{ .base = &buffer_write, .len = buffer_write.len },
162 };
163 var buffer_read = [_]u8{0} ** 128;
164 var iovecs_read = [_]iovec{
165 iovec{ .base = &buffer_read, .len = buffer_read.len },
166 };
167
168 const sqe_writev = try ring.writev(0xdddddddd, fd, iovecs_write[0..], 17);
169 try testing.expectEqual(linux.IORING_OP.WRITEV, sqe_writev.opcode);
170 try testing.expectEqual(@as(u64, 17), sqe_writev.off);
171 sqe_writev.flags |= linux.IOSQE_IO_LINK;
172
173 const sqe_fsync = try ring.fsync(0xeeeeeeee, fd, 0);
174 try testing.expectEqual(linux.IORING_OP.FSYNC, sqe_fsync.opcode);
175 try testing.expectEqual(fd, sqe_fsync.fd);
176 sqe_fsync.flags |= linux.IOSQE_IO_LINK;
177
178 const sqe_readv = try ring.read(0xffffffff, fd, .{ .iovecs = iovecs_read[0..] }, 17);
179 try testing.expectEqual(linux.IORING_OP.READV, sqe_readv.opcode);
180 try testing.expectEqual(@as(u64, 17), sqe_readv.off);
181
182 try testing.expectEqual(@as(u32, 3), ring.sq_ready());
183 try testing.expectEqual(@as(u32, 3), try ring.submit_and_wait(3));
184 try testing.expectEqual(@as(u32, 0), ring.sq_ready());
185 try testing.expectEqual(@as(u32, 3), ring.cq_ready());
186
187 try testing.expectEqual(linux.io_uring_cqe{
188 .user_data = 0xdddddddd,
189 .res = buffer_write.len,
190 .flags = 0,
191 }, try ring.copy_cqe());
192 try testing.expectEqual(@as(u32, 2), ring.cq_ready());
193
194 try testing.expectEqual(linux.io_uring_cqe{
195 .user_data = 0xeeeeeeee,
196 .res = 0,
197 .flags = 0,
198 }, try ring.copy_cqe());
199 try testing.expectEqual(@as(u32, 1), ring.cq_ready());
200
201 try testing.expectEqual(linux.io_uring_cqe{
202 .user_data = 0xffffffff,
203 .res = buffer_read.len,
204 .flags = 0,
205 }, try ring.copy_cqe());
206 try testing.expectEqual(@as(u32, 0), ring.cq_ready());
207
208 try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]);
209}
210
211test "write/read" {
212 const io = testing.io;
213
214 var ring = IoUring.init(2, 0) catch |err| switch (err) {
215 error.SystemOutdated => return error.SkipZigTest,
216 error.PermissionDenied => return error.SkipZigTest,
217 else => return err,
218 };
219 defer ring.deinit();
220
221 var tmp = std.testing.tmpDir(.{});
222 defer tmp.cleanup();
223 const path = "test_io_uring_write_read";
224 const file = try tmp.dir.createFile(io, path, .{ .read = true });
225 defer file.close(io);
226 const fd = file.handle;
227
228 const buffer_write = [_]u8{97} ** 20;
229 var buffer_read = [_]u8{98} ** 20;
230 const sqe_write = try ring.write(0x11111111, fd, buffer_write[0..], 10);
231 try testing.expectEqual(linux.IORING_OP.WRITE, sqe_write.opcode);
232 try testing.expectEqual(@as(u64, 10), sqe_write.off);
233 sqe_write.flags |= linux.IOSQE_IO_LINK;
234 const sqe_read = try ring.read(0x22222222, fd, .{ .buffer = buffer_read[0..] }, 10);
235 try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode);
236 try testing.expectEqual(@as(u64, 10), sqe_read.off);
237 try testing.expectEqual(@as(u32, 2), try ring.submit());
238
239 const cqe_write = try ring.copy_cqe();
240 const cqe_read = try ring.copy_cqe();
241 // Prior to Linux Kernel 5.6 this is the only way to test for read/write support:
242 // https://lwn.net/Articles/809820/
243 if (cqe_write.err() == .INVAL) return error.SkipZigTest;
244 if (cqe_read.err() == .INVAL) return error.SkipZigTest;
245 try testing.expectEqual(linux.io_uring_cqe{
246 .user_data = 0x11111111,
247 .res = buffer_write.len,
248 .flags = 0,
249 }, cqe_write);
250 try testing.expectEqual(linux.io_uring_cqe{
251 .user_data = 0x22222222,
252 .res = buffer_read.len,
253 .flags = 0,
254 }, cqe_read);
255 try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]);
256}
257
258test "splice/read" {
259 const io = testing.io;
260
261 var ring = IoUring.init(4, 0) catch |err| switch (err) {
262 error.SystemOutdated => return error.SkipZigTest,
263 error.PermissionDenied => return error.SkipZigTest,
264 else => return err,
265 };
266 defer ring.deinit();
267
268 var tmp = std.testing.tmpDir(.{});
269 const path_src = "test_io_uring_splice_src";
270 const file_src = try tmp.dir.createFile(io, path_src, .{ .read = true });
271 defer file_src.close(io);
272 const fd_src = file_src.handle;
273
274 const path_dst = "test_io_uring_splice_dst";
275 const file_dst = try tmp.dir.createFile(io, path_dst, .{ .read = true });
276 defer file_dst.close(io);
277 const fd_dst = file_dst.handle;
278
279 const buffer_write = [_]u8{97} ** 20;
280 var buffer_read = [_]u8{98} ** 20;
281 try file_src.writeStreamingAll(io, &buffer_write);
282
283 const fds = try posix.pipe();
284 const pipe_offset: u64 = std.math.maxInt(u64);
285
286 const sqe_splice_to_pipe = try ring.splice(0x11111111, fd_src, 0, fds[1], pipe_offset, buffer_write.len);
287 try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_to_pipe.opcode);
288 try testing.expectEqual(@as(u64, 0), sqe_splice_to_pipe.addr);
289 try testing.expectEqual(pipe_offset, sqe_splice_to_pipe.off);
290 sqe_splice_to_pipe.flags |= linux.IOSQE_IO_LINK;
291
292 const sqe_splice_from_pipe = try ring.splice(0x22222222, fds[0], pipe_offset, fd_dst, 10, buffer_write.len);
293 try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_from_pipe.opcode);
294 try testing.expectEqual(pipe_offset, sqe_splice_from_pipe.addr);
295 try testing.expectEqual(@as(u64, 10), sqe_splice_from_pipe.off);
296 sqe_splice_from_pipe.flags |= linux.IOSQE_IO_LINK;
297
298 const sqe_read = try ring.read(0x33333333, fd_dst, .{ .buffer = buffer_read[0..] }, 10);
299 try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode);
300 try testing.expectEqual(@as(u64, 10), sqe_read.off);
301 try testing.expectEqual(@as(u32, 3), try ring.submit());
302
303 const cqe_splice_to_pipe = try ring.copy_cqe();
304 const cqe_splice_from_pipe = try ring.copy_cqe();
305 const cqe_read = try ring.copy_cqe();
306 // Prior to Linux Kernel 5.6 this is the only way to test for splice/read support:
307 // https://lwn.net/Articles/809820/
308 if (cqe_splice_to_pipe.err() == .INVAL) return error.SkipZigTest;
309 if (cqe_splice_from_pipe.err() == .INVAL) return error.SkipZigTest;
310 if (cqe_read.err() == .INVAL) return error.SkipZigTest;
311 try testing.expectEqual(linux.io_uring_cqe{
312 .user_data = 0x11111111,
313 .res = buffer_write.len,
314 .flags = 0,
315 }, cqe_splice_to_pipe);
316 try testing.expectEqual(linux.io_uring_cqe{
317 .user_data = 0x22222222,
318 .res = buffer_write.len,
319 .flags = 0,
320 }, cqe_splice_from_pipe);
321 try testing.expectEqual(linux.io_uring_cqe{
322 .user_data = 0x33333333,
323 .res = buffer_read.len,
324 .flags = 0,
325 }, cqe_read);
326 try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]);
327}
328
329test "write_fixed/read_fixed" {
330 const io = testing.io;
331
332 var ring = IoUring.init(2, 0) catch |err| switch (err) {
333 error.SystemOutdated => return error.SkipZigTest,
334 error.PermissionDenied => return error.SkipZigTest,
335 else => return err,
336 };
337 defer ring.deinit();
338
339 var tmp = std.testing.tmpDir(.{});
340 defer tmp.cleanup();
341
342 const path = "test_io_uring_write_read_fixed";
343 const file = try tmp.dir.createFile(io, path, .{ .read = true });
344 defer file.close(io);
345 const fd = file.handle;
346
347 var raw_buffers: [2][11]u8 = undefined;
348 // First buffer will be written to the file.
349 @memset(&raw_buffers[0], 'z');
350 raw_buffers[0][0.."foobar".len].* = "foobar".*;
351
352 var buffers = [2]iovec{
353 .{ .base = &raw_buffers[0], .len = raw_buffers[0].len },
354 .{ .base = &raw_buffers[1], .len = raw_buffers[1].len },
355 };
356 ring.register_buffers(&buffers) catch |err| switch (err) {
357 error.SystemResources => {
358 // See https://github.com/ziglang/zig/issues/15362
359 return error.SkipZigTest;
360 },
361 else => |e| return e,
362 };
363
364 const sqe_write = try ring.write_fixed(0x45454545, fd, &buffers[0], 3, 0);
365 try testing.expectEqual(linux.IORING_OP.WRITE_FIXED, sqe_write.opcode);
366 try testing.expectEqual(@as(u64, 3), sqe_write.off);
367 sqe_write.flags |= linux.IOSQE_IO_LINK;
368
369 const sqe_read = try ring.read_fixed(0x12121212, fd, &buffers[1], 0, 1);
370 try testing.expectEqual(linux.IORING_OP.READ_FIXED, sqe_read.opcode);
371 try testing.expectEqual(@as(u64, 0), sqe_read.off);
372
373 try testing.expectEqual(@as(u32, 2), try ring.submit());
374
375 const cqe_write = try ring.copy_cqe();
376 const cqe_read = try ring.copy_cqe();
377
378 try testing.expectEqual(linux.io_uring_cqe{
379 .user_data = 0x45454545,
380 .res = @as(i32, @intCast(buffers[0].len)),
381 .flags = 0,
382 }, cqe_write);
383 try testing.expectEqual(linux.io_uring_cqe{
384 .user_data = 0x12121212,
385 .res = @as(i32, @intCast(buffers[1].len)),
386 .flags = 0,
387 }, cqe_read);
388
389 try testing.expectEqualSlices(u8, "\x00\x00\x00", buffers[1].base[0..3]);
390 try testing.expectEqualSlices(u8, "foobar", buffers[1].base[3..9]);
391 try testing.expectEqualSlices(u8, "zz", buffers[1].base[9..11]);
392}
393
394test "openat" {
395 var ring = IoUring.init(1, 0) catch |err| switch (err) {
396 error.SystemOutdated => return error.SkipZigTest,
397 error.PermissionDenied => return error.SkipZigTest,
398 else => return err,
399 };
400 defer ring.deinit();
401
402 var tmp = std.testing.tmpDir(.{});
403 defer tmp.cleanup();
404
405 const path = "test_io_uring_openat";
406
407 // Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014
408 const path_addr = if (builtin.zig_backend == .stage2_llvm) p: {
409 var workaround = path;
410 _ = &workaround;
411 break :p @intFromPtr(workaround);
412 } else @intFromPtr(path);
413
414 const flags: linux.O = .{ .CLOEXEC = true, .ACCMODE = .RDWR, .CREAT = true };
415 const mode: posix.mode_t = 0o666;
416 const sqe_openat = try ring.openat(0x33333333, tmp.dir.handle, path, flags, mode);
417 try testing.expectEqual(linux.io_uring_sqe{
418 .opcode = .OPENAT,
419 .flags = 0,
420 .ioprio = 0,
421 .fd = tmp.dir.handle,
422 .off = 0,
423 .addr = path_addr,
424 .len = mode,
425 .rw_flags = @bitCast(flags),
426 .user_data = 0x33333333,
427 .buf_index = 0,
428 .personality = 0,
429 .splice_fd_in = 0,
430 .addr3 = 0,
431 .resv = 0,
432 }, sqe_openat.*);
433 try testing.expectEqual(@as(u32, 1), try ring.submit());
434
435 const cqe_openat = try ring.copy_cqe();
436 try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data);
437 if (cqe_openat.err() == .INVAL) return error.SkipZigTest;
438 if (cqe_openat.err() == .BADF) return error.SkipZigTest;
439 if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res});
440 try testing.expect(cqe_openat.res > 0);
441 try testing.expectEqual(@as(u32, 0), cqe_openat.flags);
442
443 posix.close(cqe_openat.res);
444}
445
446test "close" {
447 const io = testing.io;
448
449 var ring = IoUring.init(1, 0) catch |err| switch (err) {
450 error.SystemOutdated => return error.SkipZigTest,
451 error.PermissionDenied => return error.SkipZigTest,
452 else => return err,
453 };
454 defer ring.deinit();
455
456 var tmp = std.testing.tmpDir(.{});
457 defer tmp.cleanup();
458
459 const path = "test_io_uring_close";
460 const file = try tmp.dir.createFile(io, path, .{});
461 errdefer file.close(io);
462
463 const sqe_close = try ring.close(0x44444444, file.handle);
464 try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode);
465 try testing.expectEqual(file.handle, sqe_close.fd);
466 try testing.expectEqual(@as(u32, 1), try ring.submit());
467
468 const cqe_close = try ring.copy_cqe();
469 if (cqe_close.err() == .INVAL) return error.SkipZigTest;
470 try testing.expectEqual(linux.io_uring_cqe{
471 .user_data = 0x44444444,
472 .res = 0,
473 .flags = 0,
474 }, cqe_close);
475}
476
477test "accept/connect/send/recv" {
478 const io = testing.io;
479 _ = io;
480
481 var ring = IoUring.init(16, 0) catch |err| switch (err) {
482 error.SystemOutdated => return error.SkipZigTest,
483 error.PermissionDenied => return error.SkipZigTest,
484 else => return err,
485 };
486 defer ring.deinit();
487
488 const socket_test_harness = try createSocketTestHarness(&ring);
489 defer socket_test_harness.close();
490
491 const buffer_send = [_]u8{ 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 };
492 var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 };
493
494 const sqe_send = try ring.send(0xeeeeeeee, socket_test_harness.client, buffer_send[0..], 0);
495 sqe_send.flags |= linux.IOSQE_IO_LINK;
496 _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
497 try testing.expectEqual(@as(u32, 2), try ring.submit());
498
499 const cqe_send = try ring.copy_cqe();
500 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
501 try testing.expectEqual(linux.io_uring_cqe{
502 .user_data = 0xeeeeeeee,
503 .res = buffer_send.len,
504 .flags = 0,
505 }, cqe_send);
506
507 const cqe_recv = try ring.copy_cqe();
508 if (cqe_recv.err() == .INVAL) return error.SkipZigTest;
509 try testing.expectEqual(linux.io_uring_cqe{
510 .user_data = 0xffffffff,
511 .res = buffer_recv.len,
512 // ignore IORING_CQE_F_SOCK_NONEMPTY since it is only set on some systems
513 .flags = cqe_recv.flags & linux.IORING_CQE_F_SOCK_NONEMPTY,
514 }, cqe_recv);
515
516 try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]);
517}
518
519test "sendmsg/recvmsg" {
520 var ring = IoUring.init(2, 0) catch |err| switch (err) {
521 error.SystemOutdated => return error.SkipZigTest,
522 error.PermissionDenied => return error.SkipZigTest,
523 else => return err,
524 };
525 defer ring.deinit();
526
527 var address_server: linux.sockaddr.in = .{
528 .port = 0,
529 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
530 };
531
532 const server = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0);
533 defer posix.close(server);
534 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1)));
535 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
536 try posix.bind(server, addrAny(&address_server), @sizeOf(linux.sockaddr.in));
537
538 // set address_server to the OS-chosen IP/port.
539 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
540 try posix.getsockname(server, addrAny(&address_server), &slen);
541
542 const client = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0);
543 defer posix.close(client);
544
545 const buffer_send = [_]u8{42} ** 128;
546 const iovecs_send = [_]iovec_const{
547 iovec_const{ .base = &buffer_send, .len = buffer_send.len },
548 };
549 const msg_send: linux.msghdr_const = .{
550 .name = addrAny(&address_server),
551 .namelen = @sizeOf(linux.sockaddr.in),
552 .iov = &iovecs_send,
553 .iovlen = 1,
554 .control = null,
555 .controllen = 0,
556 .flags = 0,
557 };
558 const sqe_sendmsg = try ring.sendmsg(0x11111111, client, &msg_send, 0);
559 sqe_sendmsg.flags |= linux.IOSQE_IO_LINK;
560 try testing.expectEqual(linux.IORING_OP.SENDMSG, sqe_sendmsg.opcode);
561 try testing.expectEqual(client, sqe_sendmsg.fd);
562
563 var buffer_recv = [_]u8{0} ** 128;
564 var iovecs_recv = [_]iovec{
565 iovec{ .base = &buffer_recv, .len = buffer_recv.len },
566 };
567 var address_recv: linux.sockaddr.in = .{
568 .port = 0,
569 .addr = 0,
570 };
571 var msg_recv: linux.msghdr = .{
572 .name = addrAny(&address_recv),
573 .namelen = @sizeOf(linux.sockaddr.in),
574 .iov = &iovecs_recv,
575 .iovlen = 1,
576 .control = null,
577 .controllen = 0,
578 .flags = 0,
579 };
580 const sqe_recvmsg = try ring.recvmsg(0x22222222, server, &msg_recv, 0);
581 try testing.expectEqual(linux.IORING_OP.RECVMSG, sqe_recvmsg.opcode);
582 try testing.expectEqual(server, sqe_recvmsg.fd);
583
584 try testing.expectEqual(@as(u32, 2), ring.sq_ready());
585 try testing.expectEqual(@as(u32, 2), try ring.submit_and_wait(2));
586 try testing.expectEqual(@as(u32, 0), ring.sq_ready());
587 try testing.expectEqual(@as(u32, 2), ring.cq_ready());
588
589 const cqe_sendmsg = try ring.copy_cqe();
590 if (cqe_sendmsg.res == -@as(i32, @intFromEnum(linux.E.INVAL))) return error.SkipZigTest;
591 try testing.expectEqual(linux.io_uring_cqe{
592 .user_data = 0x11111111,
593 .res = buffer_send.len,
594 .flags = 0,
595 }, cqe_sendmsg);
596
597 const cqe_recvmsg = try ring.copy_cqe();
598 if (cqe_recvmsg.res == -@as(i32, @intFromEnum(linux.E.INVAL))) return error.SkipZigTest;
599 try testing.expectEqual(linux.io_uring_cqe{
600 .user_data = 0x22222222,
601 .res = buffer_recv.len,
602 // ignore IORING_CQE_F_SOCK_NONEMPTY since it is set non-deterministically
603 .flags = cqe_recvmsg.flags & linux.IORING_CQE_F_SOCK_NONEMPTY,
604 }, cqe_recvmsg);
605
606 try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]);
607}
608
609test "timeout (after a relative time)" {
610 const io = testing.io;
611
612 var ring = IoUring.init(1, 0) catch |err| switch (err) {
613 error.SystemOutdated => return error.SkipZigTest,
614 error.PermissionDenied => return error.SkipZigTest,
615 else => return err,
616 };
617 defer ring.deinit();
618
619 const ms = 10;
620 const margin = 5;
621 const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 };
622
623 const started = try std.Io.Clock.awake.now(io);
624 const sqe = try ring.timeout(0x55555555, &ts, 0, 0);
625 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);
626 try testing.expectEqual(@as(u32, 1), try ring.submit());
627 const cqe = try ring.copy_cqe();
628 const stopped = try std.Io.Clock.awake.now(io);
629
630 try testing.expectEqual(linux.io_uring_cqe{
631 .user_data = 0x55555555,
632 .res = -@as(i32, @intFromEnum(linux.E.TIME)),
633 .flags = 0,
634 }, cqe);
635
636 // Tests should not depend on timings: skip test if outside margin.
637 const ms_elapsed = started.durationTo(stopped).toMilliseconds();
638 if (ms_elapsed > margin) return error.SkipZigTest;
639}
640
641test "timeout (after a number of completions)" {
642 var ring = IoUring.init(2, 0) catch |err| switch (err) {
643 error.SystemOutdated => return error.SkipZigTest,
644 error.PermissionDenied => return error.SkipZigTest,
645 else => return err,
646 };
647 defer ring.deinit();
648
649 const ts: linux.kernel_timespec = .{ .sec = 3, .nsec = 0 };
650 const count_completions: u64 = 1;
651 const sqe_timeout = try ring.timeout(0x66666666, &ts, count_completions, 0);
652 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode);
653 try testing.expectEqual(count_completions, sqe_timeout.off);
654 _ = try ring.nop(0x77777777);
655 try testing.expectEqual(@as(u32, 2), try ring.submit());
656
657 const cqe_nop = try ring.copy_cqe();
658 try testing.expectEqual(linux.io_uring_cqe{
659 .user_data = 0x77777777,
660 .res = 0,
661 .flags = 0,
662 }, cqe_nop);
663
664 const cqe_timeout = try ring.copy_cqe();
665 try testing.expectEqual(linux.io_uring_cqe{
666 .user_data = 0x66666666,
667 .res = 0,
668 .flags = 0,
669 }, cqe_timeout);
670}
671
672test "timeout_remove" {
673 var ring = IoUring.init(2, 0) catch |err| switch (err) {
674 error.SystemOutdated => return error.SkipZigTest,
675 error.PermissionDenied => return error.SkipZigTest,
676 else => return err,
677 };
678 defer ring.deinit();
679
680 const ts: linux.kernel_timespec = .{ .sec = 3, .nsec = 0 };
681 const sqe_timeout = try ring.timeout(0x88888888, &ts, 0, 0);
682 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe_timeout.opcode);
683 try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout.user_data);
684
685 const sqe_timeout_remove = try ring.timeout_remove(0x99999999, 0x88888888, 0);
686 try testing.expectEqual(linux.IORING_OP.TIMEOUT_REMOVE, sqe_timeout_remove.opcode);
687 try testing.expectEqual(@as(u64, 0x88888888), sqe_timeout_remove.addr);
688 try testing.expectEqual(@as(u64, 0x99999999), sqe_timeout_remove.user_data);
689
690 try testing.expectEqual(@as(u32, 2), try ring.submit());
691
692 // The order in which the CQE arrive is not clearly documented and it changed with kernel 5.18:
693 // * kernel 5.10 gives user data 0x88888888 first, 0x99999999 second
694 // * kernel 5.18 gives user data 0x99999999 first, 0x88888888 second
695
696 var cqes: [2]linux.io_uring_cqe = undefined;
697 cqes[0] = try ring.copy_cqe();
698 cqes[1] = try ring.copy_cqe();
699
700 for (cqes) |cqe| {
701 // IORING_OP_TIMEOUT_REMOVE is not supported by this kernel version:
702 // Timeout remove operations set the fd to -1, which results in EBADF before EINVAL.
703 // We use IORING_FEAT_RW_CUR_POS as a safety check here to make sure we are at least pre-5.6.
704 // We don't want to skip this test for newer kernels.
705 if (cqe.user_data == 0x99999999 and
706 cqe.err() == .BADF and
707 (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0)
708 {
709 return error.SkipZigTest;
710 }
711
712 try testing.expect(cqe.user_data == 0x88888888 or cqe.user_data == 0x99999999);
713
714 if (cqe.user_data == 0x88888888) {
715 try testing.expectEqual(linux.io_uring_cqe{
716 .user_data = 0x88888888,
717 .res = -@as(i32, @intFromEnum(linux.E.CANCELED)),
718 .flags = 0,
719 }, cqe);
720 } else if (cqe.user_data == 0x99999999) {
721 try testing.expectEqual(linux.io_uring_cqe{
722 .user_data = 0x99999999,
723 .res = 0,
724 .flags = 0,
725 }, cqe);
726 }
727 }
728}
729
730test "accept/connect/recv/link_timeout" {
731 const io = testing.io;
732 _ = io;
733
734 var ring = IoUring.init(16, 0) catch |err| switch (err) {
735 error.SystemOutdated => return error.SkipZigTest,
736 error.PermissionDenied => return error.SkipZigTest,
737 else => return err,
738 };
739 defer ring.deinit();
740
741 const socket_test_harness = try createSocketTestHarness(&ring);
742 defer socket_test_harness.close();
743
744 var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 };
745
746 const sqe_recv = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
747 sqe_recv.flags |= linux.IOSQE_IO_LINK;
748
749 const ts = linux.kernel_timespec{ .sec = 0, .nsec = 1000000 };
750 _ = try ring.link_timeout(0x22222222, &ts, 0);
751
752 const nr_wait = try ring.submit();
753 try testing.expectEqual(@as(u32, 2), nr_wait);
754
755 var i: usize = 0;
756 while (i < nr_wait) : (i += 1) {
757 const cqe = try ring.copy_cqe();
758 switch (cqe.user_data) {
759 0xffffffff => {
760 if (cqe.res != -@as(i32, @intFromEnum(linux.E.INTR)) and
761 cqe.res != -@as(i32, @intFromEnum(linux.E.CANCELED)))
762 {
763 std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res });
764 try testing.expect(false);
765 }
766 },
767 0x22222222 => {
768 if (cqe.res != -@as(i32, @intFromEnum(linux.E.ALREADY)) and
769 cqe.res != -@as(i32, @intFromEnum(linux.E.TIME)))
770 {
771 std.debug.print("Req 0x{x} got {d}\n", .{ cqe.user_data, cqe.res });
772 try testing.expect(false);
773 }
774 },
775 else => @panic("should not happen"),
776 }
777 }
778}
779
780test "fallocate" {
781 const io = testing.io;
782
783 var ring = IoUring.init(1, 0) catch |err| switch (err) {
784 error.SystemOutdated => return error.SkipZigTest,
785 error.PermissionDenied => return error.SkipZigTest,
786 else => return err,
787 };
788 defer ring.deinit();
789
790 var tmp = std.testing.tmpDir(.{});
791 defer tmp.cleanup();
792
793 const path = "test_io_uring_fallocate";
794 const file = try tmp.dir.createFile(io, path, .{});
795 defer file.close(io);
796
797 try testing.expectEqual(@as(u64, 0), (try file.stat(io)).size);
798
799 const len: u64 = 65536;
800 const sqe = try ring.fallocate(0xaaaaaaaa, file.handle, 0, 0, len);
801 try testing.expectEqual(linux.IORING_OP.FALLOCATE, sqe.opcode);
802 try testing.expectEqual(file.handle, sqe.fd);
803 try testing.expectEqual(@as(u32, 1), try ring.submit());
804
805 const cqe = try ring.copy_cqe();
806 switch (cqe.err()) {
807 .SUCCESS => {},
808 // This kernel's io_uring does not yet implement fallocate():
809 .INVAL => return error.SkipZigTest,
810 // This kernel does not implement fallocate():
811 .NOSYS => return error.SkipZigTest,
812 // The filesystem containing the file referred to by fd does not support this operation;
813 // or the mode is not supported by the filesystem containing the file referred to by fd:
814 .OPNOTSUPP => return error.SkipZigTest,
815 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
816 }
817 try testing.expectEqual(linux.io_uring_cqe{
818 .user_data = 0xaaaaaaaa,
819 .res = 0,
820 .flags = 0,
821 }, cqe);
822
823 try testing.expectEqual(len, (try file.stat(io)).size);
824}
825
826test "statx" {
827 const io = testing.io;
828
829 var ring = IoUring.init(1, 0) catch |err| switch (err) {
830 error.SystemOutdated => return error.SkipZigTest,
831 error.PermissionDenied => return error.SkipZigTest,
832 else => return err,
833 };
834 defer ring.deinit();
835
836 var tmp = std.testing.tmpDir(.{});
837 defer tmp.cleanup();
838 const path = "test_io_uring_statx";
839 const file = try tmp.dir.createFile(io, path, .{});
840 defer file.close(io);
841
842 try testing.expectEqual(@as(u64, 0), (try file.stat(io)).size);
843
844 try file.writeStreamingAll(io, "foobar");
845
846 var buf: linux.Statx = undefined;
847 const sqe = try ring.statx(
848 0xaaaaaaaa,
849 tmp.dir.handle,
850 path,
851 0,
852 .{ .SIZE = true },
853 &buf,
854 );
855 try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode);
856 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
857 try testing.expectEqual(@as(u32, 1), try ring.submit());
858
859 const cqe = try ring.copy_cqe();
860 switch (cqe.err()) {
861 .SUCCESS => {},
862 // This kernel's io_uring does not yet implement statx():
863 .INVAL => return error.SkipZigTest,
864 // This kernel does not implement statx():
865 .NOSYS => return error.SkipZigTest,
866 // The filesystem containing the file referred to by fd does not support this operation;
867 // or the mode is not supported by the filesystem containing the file referred to by fd:
868 .OPNOTSUPP => return error.SkipZigTest,
869 // not supported on older kernels (5.4)
870 .BADF => return error.SkipZigTest,
871 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
872 }
873 try testing.expectEqual(linux.io_uring_cqe{
874 .user_data = 0xaaaaaaaa,
875 .res = 0,
876 .flags = 0,
877 }, cqe);
878
879 try testing.expect(buf.mask.SIZE);
880 try testing.expectEqual(@as(u64, 6), buf.size);
881}
882
883test "accept/connect/recv/cancel" {
884 const io = testing.io;
885 _ = io;
886
887 var ring = IoUring.init(16, 0) catch |err| switch (err) {
888 error.SystemOutdated => return error.SkipZigTest,
889 error.PermissionDenied => return error.SkipZigTest,
890 else => return err,
891 };
892 defer ring.deinit();
893
894 const socket_test_harness = try createSocketTestHarness(&ring);
895 defer socket_test_harness.close();
896
897 var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 };
898
899 _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
900 try testing.expectEqual(@as(u32, 1), try ring.submit());
901
902 const sqe_cancel = try ring.cancel(0x99999999, 0xffffffff, 0);
903 try testing.expectEqual(linux.IORING_OP.ASYNC_CANCEL, sqe_cancel.opcode);
904 try testing.expectEqual(@as(u64, 0xffffffff), sqe_cancel.addr);
905 try testing.expectEqual(@as(u64, 0x99999999), sqe_cancel.user_data);
906 try testing.expectEqual(@as(u32, 1), try ring.submit());
907
908 var cqe_recv = try ring.copy_cqe();
909 if (cqe_recv.err() == .INVAL) return error.SkipZigTest;
910 var cqe_cancel = try ring.copy_cqe();
911 if (cqe_cancel.err() == .INVAL) return error.SkipZigTest;
912
913 // The recv/cancel CQEs may arrive in any order, the recv CQE will sometimes come first:
914 if (cqe_recv.user_data == 0x99999999 and cqe_cancel.user_data == 0xffffffff) {
915 const a = cqe_recv;
916 const b = cqe_cancel;
917 cqe_recv = b;
918 cqe_cancel = a;
919 }
920
921 try testing.expectEqual(linux.io_uring_cqe{
922 .user_data = 0xffffffff,
923 .res = -@as(i32, @intFromEnum(linux.E.CANCELED)),
924 .flags = 0,
925 }, cqe_recv);
926
927 try testing.expectEqual(linux.io_uring_cqe{
928 .user_data = 0x99999999,
929 .res = 0,
930 .flags = 0,
931 }, cqe_cancel);
932}
933
934test "register_files_update" {
935 var ring = IoUring.init(1, 0) catch |err| switch (err) {
936 error.SystemOutdated => return error.SkipZigTest,
937 error.PermissionDenied => return error.SkipZigTest,
938 else => return err,
939 };
940 defer ring.deinit();
941
942 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
943 defer posix.close(fd);
944
945 var registered_fds = [_]linux.fd_t{0} ** 2;
946 const fd_index = 0;
947 const fd_index2 = 1;
948 registered_fds[fd_index] = fd;
949 registered_fds[fd_index2] = -1;
950
951 ring.register_files(registered_fds[0..]) catch |err| switch (err) {
952 // Happens when the kernel doesn't support sparse entry (-1) in the file descriptors array.
953 error.FileDescriptorInvalid => return error.SkipZigTest,
954 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
955 };
956
957 // Test IORING_REGISTER_FILES_UPDATE
958 // Only available since Linux 5.5
959
960 const fd2 = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
961 defer posix.close(fd2);
962
963 registered_fds[fd_index] = fd2;
964 registered_fds[fd_index2] = -1;
965 try ring.register_files_update(0, registered_fds[0..]);
966
967 var buffer = [_]u8{42} ** 128;
968 {
969 const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0);
970 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
971 sqe.flags |= linux.IOSQE_FIXED_FILE;
972
973 try testing.expectEqual(@as(u32, 1), try ring.submit());
974 try testing.expectEqual(linux.io_uring_cqe{
975 .user_data = 0xcccccccc,
976 .res = buffer.len,
977 .flags = 0,
978 }, try ring.copy_cqe());
979 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]);
980 }
981
982 // Test with a non-zero offset
983
984 registered_fds[fd_index] = -1;
985 registered_fds[fd_index2] = -1;
986 try ring.register_files_update(1, registered_fds[1..]);
987
988 {
989 // Next read should still work since fd_index in the registered file descriptors hasn't been updated yet.
990 const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0);
991 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
992 sqe.flags |= linux.IOSQE_FIXED_FILE;
993
994 try testing.expectEqual(@as(u32, 1), try ring.submit());
995 try testing.expectEqual(linux.io_uring_cqe{
996 .user_data = 0xcccccccc,
997 .res = buffer.len,
998 .flags = 0,
999 }, try ring.copy_cqe());
1000 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer.len), buffer[0..]);
1001 }
1002
1003 try ring.register_files_update(0, registered_fds[0..]);
1004
1005 {
1006 // Now this should fail since both fds are sparse (-1)
1007 const sqe = try ring.read(0xcccccccc, fd_index, .{ .buffer = &buffer }, 0);
1008 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1009 sqe.flags |= linux.IOSQE_FIXED_FILE;
1010
1011 try testing.expectEqual(@as(u32, 1), try ring.submit());
1012 const cqe = try ring.copy_cqe();
1013 try testing.expectEqual(linux.E.BADF, cqe.err());
1014 }
1015
1016 try ring.unregister_files();
1017}
1018
1019test "shutdown" {
1020 var ring = IoUring.init(16, 0) catch |err| switch (err) {
1021 error.SystemOutdated => return error.SkipZigTest,
1022 error.PermissionDenied => return error.SkipZigTest,
1023 else => return err,
1024 };
1025 defer ring.deinit();
1026
1027 var address: linux.sockaddr.in = .{
1028 .port = 0,
1029 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
1030 };
1031
1032 // Socket bound, expect shutdown to work
1033 {
1034 const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1035 defer posix.close(server);
1036 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
1037 try posix.bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in));
1038 try posix.listen(server, 1);
1039
1040 // set address to the OS-chosen IP/port.
1041 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
1042 try posix.getsockname(server, addrAny(&address), &slen);
1043
1044 const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD);
1045 try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode);
1046 try testing.expectEqual(@as(i32, server), shutdown_sqe.fd);
1047
1048 try testing.expectEqual(@as(u32, 1), try ring.submit());
1049
1050 const cqe = try ring.copy_cqe();
1051 switch (cqe.err()) {
1052 .SUCCESS => {},
1053 // This kernel's io_uring does not yet implement shutdown (kernel version < 5.11)
1054 .INVAL => return error.SkipZigTest,
1055 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1056 }
1057
1058 try testing.expectEqual(linux.io_uring_cqe{
1059 .user_data = 0x445445445,
1060 .res = 0,
1061 .flags = 0,
1062 }, cqe);
1063 }
1064
1065 // Socket not bound, expect to fail with ENOTCONN
1066 {
1067 const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1068 defer posix.close(server);
1069
1070 const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) {
1071 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1072 };
1073 try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode);
1074 try testing.expectEqual(@as(i32, server), shutdown_sqe.fd);
1075
1076 try testing.expectEqual(@as(u32, 1), try ring.submit());
1077
1078 const cqe = try ring.copy_cqe();
1079 try testing.expectEqual(@as(u64, 0x445445445), cqe.user_data);
1080 try testing.expectEqual(linux.E.NOTCONN, cqe.err());
1081 }
1082}
1083
1084test "renameat" {
1085 const io = testing.io;
1086
1087 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1088 error.SystemOutdated => return error.SkipZigTest,
1089 error.PermissionDenied => return error.SkipZigTest,
1090 else => return err,
1091 };
1092 defer ring.deinit();
1093
1094 const old_path = "test_io_uring_renameat_old";
1095 const new_path = "test_io_uring_renameat_new";
1096
1097 var tmp = std.testing.tmpDir(.{});
1098 defer tmp.cleanup();
1099
1100 // Write old file with data
1101
1102 const old_file = try tmp.dir.createFile(io, old_path, .{});
1103 defer old_file.close(io);
1104 try old_file.writeStreamingAll(io, "hello");
1105
1106 // Submit renameat
1107
1108 const sqe = try ring.renameat(
1109 0x12121212,
1110 tmp.dir.handle,
1111 old_path,
1112 tmp.dir.handle,
1113 new_path,
1114 0,
1115 );
1116 try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode);
1117 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
1118 try testing.expectEqual(@as(i32, tmp.dir.handle), @as(i32, @bitCast(sqe.len)));
1119 try testing.expectEqual(@as(u32, 1), try ring.submit());
1120
1121 const cqe = try ring.copy_cqe();
1122 switch (cqe.err()) {
1123 .SUCCESS => {},
1124 // This kernel's io_uring does not yet implement renameat (kernel version < 5.11)
1125 .BADF, .INVAL => return error.SkipZigTest,
1126 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1127 }
1128 try testing.expectEqual(linux.io_uring_cqe{
1129 .user_data = 0x12121212,
1130 .res = 0,
1131 .flags = 0,
1132 }, cqe);
1133
1134 // Validate that the old file doesn't exist anymore
1135 try testing.expectError(error.FileNotFound, tmp.dir.openFile(io, old_path, .{}));
1136
1137 // Validate that the new file exists with the proper content
1138 var new_file_data: [16]u8 = undefined;
1139 try testing.expectEqualStrings("hello", try tmp.dir.readFile(io, new_path, &new_file_data));
1140}
1141
1142test "unlinkat" {
1143 const io = testing.io;
1144
1145 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1146 error.SystemOutdated => return error.SkipZigTest,
1147 error.PermissionDenied => return error.SkipZigTest,
1148 else => return err,
1149 };
1150 defer ring.deinit();
1151
1152 const path = "test_io_uring_unlinkat";
1153
1154 var tmp = std.testing.tmpDir(.{});
1155 defer tmp.cleanup();
1156
1157 // Write old file with data
1158
1159 const file = try tmp.dir.createFile(io, path, .{});
1160 defer file.close(io);
1161
1162 // Submit unlinkat
1163
1164 const sqe = try ring.unlinkat(
1165 0x12121212,
1166 tmp.dir.handle,
1167 path,
1168 0,
1169 );
1170 try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode);
1171 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
1172 try testing.expectEqual(@as(u32, 1), try ring.submit());
1173
1174 const cqe = try ring.copy_cqe();
1175 switch (cqe.err()) {
1176 .SUCCESS => {},
1177 // This kernel's io_uring does not yet implement unlinkat (kernel version < 5.11)
1178 .BADF, .INVAL => return error.SkipZigTest,
1179 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1180 }
1181 try testing.expectEqual(linux.io_uring_cqe{
1182 .user_data = 0x12121212,
1183 .res = 0,
1184 .flags = 0,
1185 }, cqe);
1186
1187 // Validate that the file doesn't exist anymore
1188 _ = tmp.dir.openFile(io, path, .{}) catch |err| switch (err) {
1189 error.FileNotFound => {},
1190 else => std.debug.panic("unexpected error: {}", .{err}),
1191 };
1192}
1193
1194test "mkdirat" {
1195 const io = testing.io;
1196
1197 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1198 error.SystemOutdated => return error.SkipZigTest,
1199 error.PermissionDenied => return error.SkipZigTest,
1200 else => return err,
1201 };
1202 defer ring.deinit();
1203
1204 var tmp = std.testing.tmpDir(.{});
1205 defer tmp.cleanup();
1206
1207 const path = "test_io_uring_mkdirat";
1208
1209 // Submit mkdirat
1210
1211 const sqe = try ring.mkdirat(
1212 0x12121212,
1213 tmp.dir.handle,
1214 path,
1215 0o0755,
1216 );
1217 try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode);
1218 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
1219 try testing.expectEqual(@as(u32, 1), try ring.submit());
1220
1221 const cqe = try ring.copy_cqe();
1222 switch (cqe.err()) {
1223 .SUCCESS => {},
1224 // This kernel's io_uring does not yet implement mkdirat (kernel version < 5.15)
1225 .BADF, .INVAL => return error.SkipZigTest,
1226 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1227 }
1228 try testing.expectEqual(linux.io_uring_cqe{
1229 .user_data = 0x12121212,
1230 .res = 0,
1231 .flags = 0,
1232 }, cqe);
1233
1234 // Validate that the directory exist
1235 _ = try tmp.dir.openDir(io, path, .{});
1236}
1237
1238test "symlinkat" {
1239 const io = testing.io;
1240
1241 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1242 error.SystemOutdated => return error.SkipZigTest,
1243 error.PermissionDenied => return error.SkipZigTest,
1244 else => return err,
1245 };
1246 defer ring.deinit();
1247
1248 var tmp = std.testing.tmpDir(.{});
1249 defer tmp.cleanup();
1250
1251 const path = "test_io_uring_symlinkat";
1252 const link_path = "test_io_uring_symlinkat_link";
1253
1254 const file = try tmp.dir.createFile(io, path, .{});
1255 defer file.close(io);
1256
1257 // Submit symlinkat
1258
1259 const sqe = try ring.symlinkat(
1260 0x12121212,
1261 path,
1262 tmp.dir.handle,
1263 link_path,
1264 );
1265 try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode);
1266 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
1267 try testing.expectEqual(@as(u32, 1), try ring.submit());
1268
1269 const cqe = try ring.copy_cqe();
1270 switch (cqe.err()) {
1271 .SUCCESS => {},
1272 // This kernel's io_uring does not yet implement symlinkat (kernel version < 5.15)
1273 .BADF, .INVAL => return error.SkipZigTest,
1274 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1275 }
1276 try testing.expectEqual(linux.io_uring_cqe{
1277 .user_data = 0x12121212,
1278 .res = 0,
1279 .flags = 0,
1280 }, cqe);
1281
1282 // Validate that the symlink exist
1283 _ = try tmp.dir.openFile(io, link_path, .{});
1284}
1285
1286test "linkat" {
1287 const io = testing.io;
1288
1289 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1290 error.SystemOutdated => return error.SkipZigTest,
1291 error.PermissionDenied => return error.SkipZigTest,
1292 else => return err,
1293 };
1294 defer ring.deinit();
1295
1296 var tmp = std.testing.tmpDir(.{});
1297 defer tmp.cleanup();
1298
1299 const first_path = "test_io_uring_linkat_first";
1300 const second_path = "test_io_uring_linkat_second";
1301
1302 // Write file with data
1303
1304 const first_file = try tmp.dir.createFile(io, first_path, .{});
1305 defer first_file.close(io);
1306 try first_file.writeStreamingAll(io, "hello");
1307
1308 // Submit linkat
1309
1310 const sqe = try ring.linkat(
1311 0x12121212,
1312 tmp.dir.handle,
1313 first_path,
1314 tmp.dir.handle,
1315 second_path,
1316 0,
1317 );
1318 try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode);
1319 try testing.expectEqual(@as(i32, tmp.dir.handle), sqe.fd);
1320 try testing.expectEqual(@as(i32, tmp.dir.handle), @as(i32, @bitCast(sqe.len)));
1321 try testing.expectEqual(@as(u32, 1), try ring.submit());
1322
1323 const cqe = try ring.copy_cqe();
1324 switch (cqe.err()) {
1325 .SUCCESS => {},
1326 // This kernel's io_uring does not yet implement linkat (kernel version < 5.15)
1327 .BADF, .INVAL => return error.SkipZigTest,
1328 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1329 }
1330 try testing.expectEqual(linux.io_uring_cqe{
1331 .user_data = 0x12121212,
1332 .res = 0,
1333 .flags = 0,
1334 }, cqe);
1335
1336 // Validate the second file
1337 var second_file_data: [16]u8 = undefined;
1338 try testing.expectEqualStrings("hello", try tmp.dir.readFile(io, second_path, &second_file_data));
1339}
1340
1341test "provide_buffers: read" {
1342 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1343 error.SystemOutdated => return error.SkipZigTest,
1344 error.PermissionDenied => return error.SkipZigTest,
1345 else => return err,
1346 };
1347 defer ring.deinit();
1348
1349 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
1350 defer posix.close(fd);
1351
1352 const group_id = 1337;
1353 const buffer_id = 0;
1354
1355 const buffer_len = 128;
1356
1357 var buffers: [4][buffer_len]u8 = undefined;
1358
1359 // Provide 4 buffers
1360
1361 {
1362 const sqe = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id);
1363 try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode);
1364 try testing.expectEqual(@as(i32, buffers.len), sqe.fd);
1365 try testing.expectEqual(@as(u32, buffers[0].len), sqe.len);
1366 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1367 try testing.expectEqual(@as(u32, 1), try ring.submit());
1368
1369 const cqe = try ring.copy_cqe();
1370 switch (cqe.err()) {
1371 // Happens when the kernel is < 5.7
1372 .INVAL, .BADF => return error.SkipZigTest,
1373 .SUCCESS => {},
1374 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1375 }
1376 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
1377 }
1378
1379 // Do 4 reads which should consume all buffers
1380
1381 var i: usize = 0;
1382 while (i < buffers.len) : (i += 1) {
1383 const sqe = try ring.read(0xdededede, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1384 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1385 try testing.expectEqual(@as(i32, fd), sqe.fd);
1386 try testing.expectEqual(@as(u64, 0), sqe.addr);
1387 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1388 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1389 try testing.expectEqual(@as(u32, 1), try ring.submit());
1390
1391 const cqe = try ring.copy_cqe();
1392 switch (cqe.err()) {
1393 .SUCCESS => {},
1394 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1395 }
1396
1397 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
1398 const used_buffer_id = cqe.flags >> 16;
1399 try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3);
1400 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
1401
1402 try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data);
1403 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]);
1404 }
1405
1406 // This read should fail
1407
1408 {
1409 const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1410 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1411 try testing.expectEqual(@as(i32, fd), sqe.fd);
1412 try testing.expectEqual(@as(u64, 0), sqe.addr);
1413 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1414 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1415 try testing.expectEqual(@as(u32, 1), try ring.submit());
1416
1417 const cqe = try ring.copy_cqe();
1418 switch (cqe.err()) {
1419 // Expected
1420 .NOBUFS => {},
1421 .SUCCESS => std.debug.panic("unexpected success", .{}),
1422 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1423 }
1424 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
1425 }
1426
1427 // Provide 1 buffer again
1428
1429 // Deliberately put something we don't expect in the buffers
1430 @memset(mem.sliceAsBytes(&buffers), 42);
1431
1432 const reprovided_buffer_id = 2;
1433
1434 {
1435 _ = try ring.provide_buffers(0xabababab, @as([*]u8, @ptrCast(&buffers[reprovided_buffer_id])), buffer_len, 1, group_id, reprovided_buffer_id);
1436 try testing.expectEqual(@as(u32, 1), try ring.submit());
1437
1438 const cqe = try ring.copy_cqe();
1439 switch (cqe.err()) {
1440 .SUCCESS => {},
1441 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1442 }
1443 }
1444
1445 // Final read which should work
1446
1447 {
1448 const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1449 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1450 try testing.expectEqual(@as(i32, fd), sqe.fd);
1451 try testing.expectEqual(@as(u64, 0), sqe.addr);
1452 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1453 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1454 try testing.expectEqual(@as(u32, 1), try ring.submit());
1455
1456 const cqe = try ring.copy_cqe();
1457 switch (cqe.err()) {
1458 .SUCCESS => {},
1459 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1460 }
1461
1462 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
1463 const used_buffer_id = cqe.flags >> 16;
1464 try testing.expectEqual(used_buffer_id, reprovided_buffer_id);
1465 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
1466 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
1467 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]);
1468 }
1469}
1470
1471test "remove_buffers" {
1472 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1473 error.SystemOutdated => return error.SkipZigTest,
1474 error.PermissionDenied => return error.SkipZigTest,
1475 else => return err,
1476 };
1477 defer ring.deinit();
1478
1479 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
1480 defer posix.close(fd);
1481
1482 const group_id = 1337;
1483 const buffer_id = 0;
1484
1485 const buffer_len = 128;
1486
1487 var buffers: [4][buffer_len]u8 = undefined;
1488
1489 // Provide 4 buffers
1490
1491 {
1492 _ = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id);
1493 try testing.expectEqual(@as(u32, 1), try ring.submit());
1494
1495 const cqe = try ring.copy_cqe();
1496 switch (cqe.err()) {
1497 .INVAL, .BADF => return error.SkipZigTest,
1498 .SUCCESS => {},
1499 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1500 }
1501 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
1502 }
1503
1504 // Remove 3 buffers
1505
1506 {
1507 const sqe = try ring.remove_buffers(0xbababababa, 3, group_id);
1508 try testing.expectEqual(linux.IORING_OP.REMOVE_BUFFERS, sqe.opcode);
1509 try testing.expectEqual(@as(i32, 3), sqe.fd);
1510 try testing.expectEqual(@as(u64, 0), sqe.addr);
1511 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1512 try testing.expectEqual(@as(u32, 1), try ring.submit());
1513
1514 const cqe = try ring.copy_cqe();
1515 switch (cqe.err()) {
1516 .SUCCESS => {},
1517 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1518 }
1519 try testing.expectEqual(@as(u64, 0xbababababa), cqe.user_data);
1520 }
1521
1522 // This read should work
1523
1524 {
1525 _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1526 try testing.expectEqual(@as(u32, 1), try ring.submit());
1527
1528 const cqe = try ring.copy_cqe();
1529 switch (cqe.err()) {
1530 .SUCCESS => {},
1531 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1532 }
1533
1534 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
1535 const used_buffer_id = cqe.flags >> 16;
1536 try testing.expect(used_buffer_id >= 0 and used_buffer_id < 4);
1537 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
1538 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
1539 try testing.expectEqualSlices(u8, &([_]u8{0} ** buffer_len), buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))]);
1540 }
1541
1542 // Final read should _not_ work
1543
1544 {
1545 _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1546 try testing.expectEqual(@as(u32, 1), try ring.submit());
1547
1548 const cqe = try ring.copy_cqe();
1549 switch (cqe.err()) {
1550 // Expected
1551 .NOBUFS => {},
1552 .SUCCESS => std.debug.panic("unexpected success", .{}),
1553 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1554 }
1555 }
1556}
1557
1558test "provide_buffers: accept/connect/send/recv" {
1559 const io = testing.io;
1560 _ = io;
1561
1562 var ring = IoUring.init(16, 0) catch |err| switch (err) {
1563 error.SystemOutdated => return error.SkipZigTest,
1564 error.PermissionDenied => return error.SkipZigTest,
1565 else => return err,
1566 };
1567 defer ring.deinit();
1568
1569 const group_id = 1337;
1570 const buffer_id = 0;
1571
1572 const buffer_len = 128;
1573 var buffers: [4][buffer_len]u8 = undefined;
1574
1575 // Provide 4 buffers
1576
1577 {
1578 const sqe = try ring.provide_buffers(0xcccccccc, @as([*]u8, @ptrCast(&buffers)), buffer_len, buffers.len, group_id, buffer_id);
1579 try testing.expectEqual(linux.IORING_OP.PROVIDE_BUFFERS, sqe.opcode);
1580 try testing.expectEqual(@as(i32, buffers.len), sqe.fd);
1581 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1582 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1583 try testing.expectEqual(@as(u32, 1), try ring.submit());
1584
1585 const cqe = try ring.copy_cqe();
1586 switch (cqe.err()) {
1587 // Happens when the kernel is < 5.7
1588 .INVAL => return error.SkipZigTest,
1589 // Happens on the kernel 5.4
1590 .BADF => return error.SkipZigTest,
1591 .SUCCESS => {},
1592 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1593 }
1594 try testing.expectEqual(@as(u64, 0xcccccccc), cqe.user_data);
1595 }
1596
1597 const socket_test_harness = try createSocketTestHarness(&ring);
1598 defer socket_test_harness.close();
1599
1600 // Do 4 send on the socket
1601
1602 {
1603 var i: usize = 0;
1604 while (i < buffers.len) : (i += 1) {
1605 _ = try ring.send(0xdeaddead, socket_test_harness.server, &([_]u8{'z'} ** buffer_len), 0);
1606 try testing.expectEqual(@as(u32, 1), try ring.submit());
1607 }
1608
1609 var cqes: [4]linux.io_uring_cqe = undefined;
1610 try testing.expectEqual(@as(u32, 4), try ring.copy_cqes(&cqes, 4));
1611 }
1612
1613 // Do 4 recv which should consume all buffers
1614
1615 // Deliberately put something we don't expect in the buffers
1616 @memset(mem.sliceAsBytes(&buffers), 1);
1617
1618 var i: usize = 0;
1619 while (i < buffers.len) : (i += 1) {
1620 const sqe = try ring.recv(0xdededede, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1621 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
1622 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
1623 try testing.expectEqual(@as(u64, 0), sqe.addr);
1624 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1625 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1626 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
1627 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
1628 try testing.expectEqual(@as(u32, 1), try ring.submit());
1629
1630 const cqe = try ring.copy_cqe();
1631 switch (cqe.err()) {
1632 .SUCCESS => {},
1633 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1634 }
1635
1636 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
1637 const used_buffer_id = cqe.flags >> 16;
1638 try testing.expect(used_buffer_id >= 0 and used_buffer_id <= 3);
1639 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
1640
1641 try testing.expectEqual(@as(u64, 0xdededede), cqe.user_data);
1642 const buffer = buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))];
1643 try testing.expectEqualSlices(u8, &([_]u8{'z'} ** buffer_len), buffer);
1644 }
1645
1646 // This recv should fail
1647
1648 {
1649 const sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1650 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
1651 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
1652 try testing.expectEqual(@as(u64, 0), sqe.addr);
1653 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1654 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1655 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
1656 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
1657 try testing.expectEqual(@as(u32, 1), try ring.submit());
1658
1659 const cqe = try ring.copy_cqe();
1660 switch (cqe.err()) {
1661 // Expected
1662 .NOBUFS => {},
1663 .SUCCESS => std.debug.panic("unexpected success", .{}),
1664 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1665 }
1666 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
1667 }
1668
1669 // Provide 1 buffer again
1670
1671 const reprovided_buffer_id = 2;
1672
1673 {
1674 _ = try ring.provide_buffers(0xabababab, @as([*]u8, @ptrCast(&buffers[reprovided_buffer_id])), buffer_len, 1, group_id, reprovided_buffer_id);
1675 try testing.expectEqual(@as(u32, 1), try ring.submit());
1676
1677 const cqe = try ring.copy_cqe();
1678 switch (cqe.err()) {
1679 .SUCCESS => {},
1680 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1681 }
1682 }
1683
1684 // Redo 1 send on the server socket
1685
1686 {
1687 _ = try ring.send(0xdeaddead, socket_test_harness.server, &([_]u8{'w'} ** buffer_len), 0);
1688 try testing.expectEqual(@as(u32, 1), try ring.submit());
1689
1690 _ = try ring.copy_cqe();
1691 }
1692
1693 // Final recv which should work
1694
1695 // Deliberately put something we don't expect in the buffers
1696 @memset(mem.sliceAsBytes(&buffers), 1);
1697
1698 {
1699 const sqe = try ring.recv(0xdfdfdfdf, socket_test_harness.client, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1700 try testing.expectEqual(linux.IORING_OP.RECV, sqe.opcode);
1701 try testing.expectEqual(@as(i32, socket_test_harness.client), sqe.fd);
1702 try testing.expectEqual(@as(u64, 0), sqe.addr);
1703 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
1704 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
1705 try testing.expectEqual(@as(u32, 0), sqe.rw_flags);
1706 try testing.expectEqual(@as(u32, linux.IOSQE_BUFFER_SELECT), sqe.flags);
1707 try testing.expectEqual(@as(u32, 1), try ring.submit());
1708
1709 const cqe = try ring.copy_cqe();
1710 switch (cqe.err()) {
1711 .SUCCESS => {},
1712 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
1713 }
1714
1715 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER);
1716 const used_buffer_id = cqe.flags >> 16;
1717 try testing.expectEqual(used_buffer_id, reprovided_buffer_id);
1718 try testing.expectEqual(@as(i32, buffer_len), cqe.res);
1719 try testing.expectEqual(@as(u64, 0xdfdfdfdf), cqe.user_data);
1720 const buffer = buffers[used_buffer_id][0..@as(usize, @intCast(cqe.res))];
1721 try testing.expectEqualSlices(u8, &([_]u8{'w'} ** buffer_len), buffer);
1722 }
1723}
1724
1725test "accept multishot" {
1726 var ring = IoUring.init(16, 0) catch |err| switch (err) {
1727 error.SystemOutdated => return error.SkipZigTest,
1728 error.PermissionDenied => return error.SkipZigTest,
1729 else => return err,
1730 };
1731 defer ring.deinit();
1732
1733 var address: linux.sockaddr.in = .{
1734 .port = 0,
1735 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
1736 };
1737 const listener_socket = try createListenerSocket(&address);
1738 defer posix.close(listener_socket);
1739
1740 // submit multishot accept operation
1741 var addr: posix.sockaddr = undefined;
1742 var addr_len: posix.socklen_t = @sizeOf(@TypeOf(addr));
1743 const userdata: u64 = 0xaaaaaaaa;
1744 _ = try ring.accept_multishot(userdata, listener_socket, &addr, &addr_len, 0);
1745 try testing.expectEqual(@as(u32, 1), try ring.submit());
1746
1747 var nr: usize = 4; // number of clients to connect
1748 while (nr > 0) : (nr -= 1) {
1749 // connect client
1750 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1751 errdefer posix.close(client);
1752 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
1753
1754 // test accept completion
1755 var cqe = try ring.copy_cqe();
1756 if (cqe.err() == .INVAL) return error.SkipZigTest;
1757 try testing.expect(cqe.res > 0);
1758 try testing.expect(cqe.user_data == userdata);
1759 try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE > 0); // more flag is set
1760
1761 posix.close(client);
1762 }
1763}
1764
1765test "accept/connect/send_zc/recv" {
1766 try skipKernelLessThan(.{ .major = 6, .minor = 0, .patch = 0 });
1767
1768 const io = testing.io;
1769 _ = io;
1770
1771 var ring = IoUring.init(16, 0) catch |err| switch (err) {
1772 error.SystemOutdated => return error.SkipZigTest,
1773 error.PermissionDenied => return error.SkipZigTest,
1774 else => return err,
1775 };
1776 defer ring.deinit();
1777
1778 const socket_test_harness = try createSocketTestHarness(&ring);
1779 defer socket_test_harness.close();
1780
1781 const buffer_send = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
1782 var buffer_recv = [_]u8{0} ** 10;
1783
1784 // zero-copy send
1785 const sqe_send = try ring.send_zc(0xeeeeeeee, socket_test_harness.client, buffer_send[0..], 0, 0);
1786 sqe_send.flags |= linux.IOSQE_IO_LINK;
1787 _ = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);
1788 try testing.expectEqual(@as(u32, 2), try ring.submit());
1789
1790 var cqe_send = try ring.copy_cqe();
1791 // First completion of zero-copy send.
1792 // IORING_CQE_F_MORE, means that there
1793 // will be a second completion event / notification for the
1794 // request, with the user_data field set to the same value.
1795 // buffer_send must be keep alive until second cqe.
1796 try testing.expectEqual(linux.io_uring_cqe{
1797 .user_data = 0xeeeeeeee,
1798 .res = buffer_send.len,
1799 .flags = linux.IORING_CQE_F_MORE,
1800 }, cqe_send);
1801
1802 cqe_send, const cqe_recv = brk: {
1803 const cqe1 = try ring.copy_cqe();
1804 const cqe2 = try ring.copy_cqe();
1805 break :brk if (cqe1.user_data == 0xeeeeeeee) .{ cqe1, cqe2 } else .{ cqe2, cqe1 };
1806 };
1807
1808 try testing.expectEqual(linux.io_uring_cqe{
1809 .user_data = 0xffffffff,
1810 .res = buffer_recv.len,
1811 .flags = cqe_recv.flags & linux.IORING_CQE_F_SOCK_NONEMPTY,
1812 }, cqe_recv);
1813 try testing.expectEqualSlices(u8, buffer_send[0..buffer_recv.len], buffer_recv[0..]);
1814
1815 // Second completion of zero-copy send.
1816 // IORING_CQE_F_NOTIF in flags signals that kernel is done with send_buffer
1817 try testing.expectEqual(linux.io_uring_cqe{
1818 .user_data = 0xeeeeeeee,
1819 .res = 0,
1820 .flags = linux.IORING_CQE_F_NOTIF,
1821 }, cqe_send);
1822}
1823
1824test "accept_direct" {
1825 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
1826
1827 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1828 error.SystemOutdated => return error.SkipZigTest,
1829 error.PermissionDenied => return error.SkipZigTest,
1830 else => return err,
1831 };
1832 defer ring.deinit();
1833 var address: linux.sockaddr.in = .{
1834 .port = 0,
1835 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
1836 };
1837
1838 // register direct file descriptors
1839 var registered_fds = [_]linux.fd_t{-1} ** 2;
1840 try ring.register_files(registered_fds[0..]);
1841
1842 const listener_socket = try createListenerSocket(&address);
1843 defer posix.close(listener_socket);
1844
1845 const accept_userdata: u64 = 0xaaaaaaaa;
1846 const read_userdata: u64 = 0xbbbbbbbb;
1847 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
1848
1849 for (0..2) |_| {
1850 for (registered_fds, 0..) |_, i| {
1851 var buffer_recv = [_]u8{0} ** 16;
1852 const buffer_send: []const u8 = data[0 .. data.len - i]; // make it different at each loop
1853
1854 // submit accept, will chose registered fd and return index in cqe
1855 _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0);
1856 try testing.expectEqual(@as(u32, 1), try ring.submit());
1857
1858 // connect
1859 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1860 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
1861 defer posix.close(client);
1862
1863 // accept completion
1864 const cqe_accept = try ring.copy_cqe();
1865 try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err());
1866 const fd_index = cqe_accept.res;
1867 try testing.expect(fd_index < registered_fds.len);
1868 try testing.expect(cqe_accept.user_data == accept_userdata);
1869
1870 // send data
1871 _ = try posix.send(client, buffer_send, 0);
1872
1873 // Example of how to use registered fd:
1874 // Submit receive to fixed file returned by accept (fd_index).
1875 // Fd field is set to registered file index, returned by accept.
1876 // Flag linux.IOSQE_FIXED_FILE must be set.
1877 const recv_sqe = try ring.recv(read_userdata, fd_index, .{ .buffer = &buffer_recv }, 0);
1878 recv_sqe.flags |= linux.IOSQE_FIXED_FILE;
1879 try testing.expectEqual(@as(u32, 1), try ring.submit());
1880
1881 // accept receive
1882 const recv_cqe = try ring.copy_cqe();
1883 try testing.expect(recv_cqe.user_data == read_userdata);
1884 try testing.expect(recv_cqe.res == buffer_send.len);
1885 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]);
1886 }
1887 // no more available fds, accept will get NFILE error
1888 {
1889 // submit accept
1890 _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0);
1891 try testing.expectEqual(@as(u32, 1), try ring.submit());
1892 // connect
1893 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1894 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
1895 defer posix.close(client);
1896 // completion with error
1897 const cqe_accept = try ring.copy_cqe();
1898 try testing.expect(cqe_accept.user_data == accept_userdata);
1899 try testing.expectEqual(posix.E.NFILE, cqe_accept.err());
1900 }
1901 // return file descriptors to kernel
1902 try ring.register_files_update(0, registered_fds[0..]);
1903 }
1904 try ring.unregister_files();
1905}
1906
1907test "accept_multishot_direct" {
1908 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
1909
1910 if (builtin.cpu.arch == .riscv64) {
1911 // https://github.com/ziglang/zig/issues/25734
1912 return error.SkipZigTest;
1913 }
1914
1915 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1916 error.SystemOutdated => return error.SkipZigTest,
1917 error.PermissionDenied => return error.SkipZigTest,
1918 else => return err,
1919 };
1920 defer ring.deinit();
1921
1922 var address: linux.sockaddr.in = .{
1923 .port = 0,
1924 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
1925 };
1926
1927 var registered_fds = [_]linux.fd_t{-1} ** 2;
1928 try ring.register_files(registered_fds[0..]);
1929
1930 const listener_socket = try createListenerSocket(&address);
1931 defer posix.close(listener_socket);
1932
1933 const accept_userdata: u64 = 0xaaaaaaaa;
1934
1935 for (0..2) |_| {
1936 // submit multishot accept
1937 // Will chose registered fd and return index of the selected registered file in cqe.
1938 _ = try ring.accept_multishot_direct(accept_userdata, listener_socket, null, null, 0);
1939 try testing.expectEqual(@as(u32, 1), try ring.submit());
1940
1941 for (registered_fds) |_| {
1942 // connect
1943 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1944 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
1945 defer posix.close(client);
1946
1947 // accept completion
1948 const cqe_accept = try ring.copy_cqe();
1949 const fd_index = cqe_accept.res;
1950 try testing.expect(fd_index < registered_fds.len);
1951 try testing.expect(cqe_accept.user_data == accept_userdata);
1952 try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE > 0); // has more is set
1953 }
1954 // No more available fds, accept will get NFILE error.
1955 // Multishot is terminated (more flag is not set).
1956 {
1957 // connect
1958 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
1959 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
1960 defer posix.close(client);
1961 // completion with error
1962 const cqe_accept = try ring.copy_cqe();
1963 try testing.expect(cqe_accept.user_data == accept_userdata);
1964 try testing.expectEqual(posix.E.NFILE, cqe_accept.err());
1965 try testing.expect(cqe_accept.flags & linux.IORING_CQE_F_MORE == 0); // has more is not set
1966 }
1967 // return file descriptors to kernel
1968 try ring.register_files_update(0, registered_fds[0..]);
1969 }
1970 try ring.unregister_files();
1971}
1972
1973test "socket" {
1974 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
1975
1976 var ring = IoUring.init(1, 0) catch |err| switch (err) {
1977 error.SystemOutdated => return error.SkipZigTest,
1978 error.PermissionDenied => return error.SkipZigTest,
1979 else => return err,
1980 };
1981 defer ring.deinit();
1982
1983 // prepare, submit socket operation
1984 _ = try ring.socket(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0);
1985 try testing.expectEqual(@as(u32, 1), try ring.submit());
1986
1987 // test completion
1988 var cqe = try ring.copy_cqe();
1989 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
1990 const fd: linux.fd_t = @intCast(cqe.res);
1991 try testing.expect(fd > 2);
1992
1993 posix.close(fd);
1994}
1995
1996test "socket_direct/socket_direct_alloc/close_direct" {
1997 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
1998
1999 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2000 error.SystemOutdated => return error.SkipZigTest,
2001 error.PermissionDenied => return error.SkipZigTest,
2002 else => return err,
2003 };
2004 defer ring.deinit();
2005
2006 var registered_fds = [_]linux.fd_t{-1} ** 3;
2007 try ring.register_files(registered_fds[0..]);
2008
2009 // create socket in registered file descriptor at index 0 (last param)
2010 _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 0);
2011 try testing.expectEqual(@as(u32, 1), try ring.submit());
2012 var cqe_socket = try ring.copy_cqe();
2013 try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err());
2014 try testing.expect(cqe_socket.res == 0);
2015
2016 // create socket in registered file descriptor at index 1 (last param)
2017 _ = try ring.socket_direct(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0, 1);
2018 try testing.expectEqual(@as(u32, 1), try ring.submit());
2019 cqe_socket = try ring.copy_cqe();
2020 try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err());
2021 try testing.expect(cqe_socket.res == 0); // res is 0 when index is specified
2022
2023 // create socket in kernel chosen file descriptor index (_alloc version)
2024 // completion res has index from registered files
2025 _ = try ring.socket_direct_alloc(0, linux.AF.INET, posix.SOCK.STREAM, 0, 0);
2026 try testing.expectEqual(@as(u32, 1), try ring.submit());
2027 cqe_socket = try ring.copy_cqe();
2028 try testing.expectEqual(posix.E.SUCCESS, cqe_socket.err());
2029 try testing.expect(cqe_socket.res == 2); // returns registered file index
2030
2031 // use sockets from registered_fds in connect operation
2032 var address: linux.sockaddr.in = .{
2033 .port = 0,
2034 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
2035 };
2036 const listener_socket = try createListenerSocket(&address);
2037 defer posix.close(listener_socket);
2038 const accept_userdata: u64 = 0xaaaaaaaa;
2039 const connect_userdata: u64 = 0xbbbbbbbb;
2040 const close_userdata: u64 = 0xcccccccc;
2041 for (registered_fds, 0..) |_, fd_index| {
2042 // prepare accept
2043 _ = try ring.accept(accept_userdata, listener_socket, null, null, 0);
2044 // prepare connect with fixed socket
2045 const connect_sqe = try ring.connect(connect_userdata, @intCast(fd_index), addrAny(&address), @sizeOf(linux.sockaddr.in));
2046 connect_sqe.flags |= linux.IOSQE_FIXED_FILE; // fd is fixed file index
2047 // submit both
2048 try testing.expectEqual(@as(u32, 2), try ring.submit());
2049 // get completions
2050 var cqe_connect = try ring.copy_cqe();
2051 var cqe_accept = try ring.copy_cqe();
2052 // ignore order
2053 if (cqe_connect.user_data == accept_userdata and cqe_accept.user_data == connect_userdata) {
2054 const a = cqe_accept;
2055 const b = cqe_connect;
2056 cqe_accept = b;
2057 cqe_connect = a;
2058 }
2059 // test connect completion
2060 try testing.expect(cqe_connect.user_data == connect_userdata);
2061 try testing.expectEqual(posix.E.SUCCESS, cqe_connect.err());
2062 // test accept completion
2063 try testing.expect(cqe_accept.user_data == accept_userdata);
2064 try testing.expectEqual(posix.E.SUCCESS, cqe_accept.err());
2065
2066 // submit and test close_direct
2067 _ = try ring.close_direct(close_userdata, @intCast(fd_index));
2068 try testing.expectEqual(@as(u32, 1), try ring.submit());
2069 var cqe_close = try ring.copy_cqe();
2070 try testing.expect(cqe_close.user_data == close_userdata);
2071 try testing.expectEqual(posix.E.SUCCESS, cqe_close.err());
2072 }
2073
2074 try ring.unregister_files();
2075}
2076
2077test "openat_direct/close_direct" {
2078 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
2079
2080 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2081 error.SystemOutdated => return error.SkipZigTest,
2082 error.PermissionDenied => return error.SkipZigTest,
2083 else => return err,
2084 };
2085 defer ring.deinit();
2086
2087 var registered_fds = [_]linux.fd_t{-1} ** 3;
2088 try ring.register_files(registered_fds[0..]);
2089
2090 var tmp = std.testing.tmpDir(.{});
2091 defer tmp.cleanup();
2092 const path = "test_io_uring_close_direct";
2093 const flags: linux.O = .{ .ACCMODE = .RDWR, .CREAT = true };
2094 const mode: posix.mode_t = 0o666;
2095 const user_data: u64 = 0;
2096
2097 // use registered file at index 0 (last param)
2098 _ = try ring.openat_direct(user_data, tmp.dir.handle, path, flags, mode, 0);
2099 try testing.expectEqual(@as(u32, 1), try ring.submit());
2100 var cqe = try ring.copy_cqe();
2101 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2102 try testing.expect(cqe.res == 0);
2103
2104 // use registered file at index 1
2105 _ = try ring.openat_direct(user_data, tmp.dir.handle, path, flags, mode, 1);
2106 try testing.expectEqual(@as(u32, 1), try ring.submit());
2107 cqe = try ring.copy_cqe();
2108 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2109 try testing.expect(cqe.res == 0); // res is 0 when we specify index
2110
2111 // let kernel choose registered file index
2112 _ = try ring.openat_direct(user_data, tmp.dir.handle, path, flags, mode, linux.IORING_FILE_INDEX_ALLOC);
2113 try testing.expectEqual(@as(u32, 1), try ring.submit());
2114 cqe = try ring.copy_cqe();
2115 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2116 try testing.expect(cqe.res == 2); // chosen index is in res
2117
2118 // close all open file descriptors
2119 for (registered_fds, 0..) |_, fd_index| {
2120 _ = try ring.close_direct(user_data, @intCast(fd_index));
2121 try testing.expectEqual(@as(u32, 1), try ring.submit());
2122 var cqe_close = try ring.copy_cqe();
2123 try testing.expectEqual(posix.E.SUCCESS, cqe_close.err());
2124 }
2125 try ring.unregister_files();
2126}
2127
2128test "ring mapped buffers recv" {
2129 const io = testing.io;
2130 _ = io;
2131
2132 var ring = IoUring.init(16, 0) catch |err| switch (err) {
2133 error.SystemOutdated => return error.SkipZigTest,
2134 error.PermissionDenied => return error.SkipZigTest,
2135 else => return err,
2136 };
2137 defer ring.deinit();
2138
2139 // init buffer group
2140 const group_id: u16 = 1; // buffers group id
2141 const buffers_count: u16 = 2; // number of buffers in buffer group
2142 const buffer_size: usize = 4; // size of each buffer in group
2143 var buf_grp = BufferGroup.init(
2144 &ring,
2145 testing.allocator,
2146 group_id,
2147 buffer_size,
2148 buffers_count,
2149 ) catch |err| switch (err) {
2150 // kernel older than 5.19
2151 error.ArgumentsInvalid => return error.SkipZigTest,
2152 else => return err,
2153 };
2154 defer buf_grp.deinit(testing.allocator);
2155
2156 // create client/server fds
2157 const fds = try createSocketTestHarness(&ring);
2158 defer fds.close();
2159
2160 // for random user_data in sqe/cqe
2161 var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed);
2162 var rnd = Rnd.random();
2163
2164 var round: usize = 4; // repeat send/recv cycle round times
2165 while (round > 0) : (round -= 1) {
2166 // client sends data
2167 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
2168 {
2169 const user_data = rnd.int(u64);
2170 _ = try ring.send(user_data, fds.client, data[0..], 0);
2171 try testing.expectEqual(@as(u32, 1), try ring.submit());
2172 const cqe_send = try ring.copy_cqe();
2173 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
2174 try testing.expectEqual(linux.io_uring_cqe{ .user_data = user_data, .res = data.len, .flags = 0 }, cqe_send);
2175 }
2176 var pos: usize = 0;
2177
2178 // read first chunk
2179 const cqe1 = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64));
2180 var buf = try buf_grp.get(cqe1);
2181 try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf);
2182 pos += buf.len;
2183 // second chunk
2184 const cqe2 = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64));
2185 buf = try buf_grp.get(cqe2);
2186 try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf);
2187 pos += buf.len;
2188
2189 // both buffers provided to the kernel are used so we get error
2190 // 'no more buffers', until we put buffers to the kernel
2191 {
2192 const user_data = rnd.int(u64);
2193 _ = try buf_grp.recv(user_data, fds.server, 0);
2194 try testing.expectEqual(@as(u32, 1), try ring.submit());
2195 const cqe = try ring.copy_cqe();
2196 try testing.expectEqual(user_data, cqe.user_data);
2197 try testing.expect(cqe.res < 0); // fail
2198 try testing.expectEqual(posix.E.NOBUFS, cqe.err());
2199 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only
2200 try testing.expectError(error.NoBufferSelected, cqe.buffer_id());
2201 }
2202
2203 // put buffers back to the kernel
2204 try buf_grp.put(cqe1);
2205 try buf_grp.put(cqe2);
2206
2207 // read remaining data
2208 while (pos < data.len) {
2209 const cqe = try buf_grp_recv_submit_get_cqe(&ring, &buf_grp, fds.server, rnd.int(u64));
2210 buf = try buf_grp.get(cqe);
2211 try testing.expectEqualSlices(u8, data[pos..][0..buf.len], buf);
2212 pos += buf.len;
2213 try buf_grp.put(cqe);
2214 }
2215 }
2216}
2217
2218test "ring mapped buffers multishot recv" {
2219 const io = testing.io;
2220 _ = io;
2221
2222 var ring = IoUring.init(16, 0) catch |err| switch (err) {
2223 error.SystemOutdated => return error.SkipZigTest,
2224 error.PermissionDenied => return error.SkipZigTest,
2225 else => return err,
2226 };
2227 defer ring.deinit();
2228
2229 // init buffer group
2230 const group_id: u16 = 1; // buffers group id
2231 const buffers_count: u16 = 2; // number of buffers in buffer group
2232 const buffer_size: usize = 4; // size of each buffer in group
2233 var buf_grp = BufferGroup.init(
2234 &ring,
2235 testing.allocator,
2236 group_id,
2237 buffer_size,
2238 buffers_count,
2239 ) catch |err| switch (err) {
2240 // kernel older than 5.19
2241 error.ArgumentsInvalid => return error.SkipZigTest,
2242 else => return err,
2243 };
2244 defer buf_grp.deinit(testing.allocator);
2245
2246 // create client/server fds
2247 const fds = try createSocketTestHarness(&ring);
2248 defer fds.close();
2249
2250 // for random user_data in sqe/cqe
2251 var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed);
2252 var rnd = Rnd.random();
2253
2254 var round: usize = 4; // repeat send/recv cycle round times
2255 while (round > 0) : (round -= 1) {
2256 // client sends data
2257 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
2258 {
2259 const user_data = rnd.int(u64);
2260 _ = try ring.send(user_data, fds.client, data[0..], 0);
2261 try testing.expectEqual(@as(u32, 1), try ring.submit());
2262 const cqe_send = try ring.copy_cqe();
2263 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
2264 try testing.expectEqual(linux.io_uring_cqe{ .user_data = user_data, .res = data.len, .flags = 0 }, cqe_send);
2265 }
2266
2267 // start multishot recv
2268 var recv_user_data = rnd.int(u64);
2269 _ = try buf_grp.recv_multishot(recv_user_data, fds.server, 0);
2270 try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit
2271
2272 // server reads data into provided buffers
2273 // there are 2 buffers of size 4, so each read gets only chunk of data
2274 // we read four chunks of 4, 4, 4, 4 bytes each
2275 var chunk: []const u8 = data[0..buffer_size]; // first chunk
2276 const cqe1 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
2277 try testing.expect(cqe1.flags & linux.IORING_CQE_F_MORE > 0);
2278
2279 chunk = data[buffer_size .. buffer_size * 2]; // second chunk
2280 const cqe2 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
2281 try testing.expect(cqe2.flags & linux.IORING_CQE_F_MORE > 0);
2282
2283 // both buffers provided to the kernel are used so we get error
2284 // 'no more buffers', until we put buffers to the kernel
2285 {
2286 const cqe = try ring.copy_cqe();
2287 try testing.expectEqual(recv_user_data, cqe.user_data);
2288 try testing.expect(cqe.res < 0); // fail
2289 try testing.expectEqual(posix.E.NOBUFS, cqe.err());
2290 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == 0); // IORING_CQE_F_BUFFER flags is set on success only
2291 // has more is not set
2292 // indicates that multishot is finished
2293 try testing.expect(cqe.flags & linux.IORING_CQE_F_MORE == 0);
2294 try testing.expectError(error.NoBufferSelected, cqe.buffer_id());
2295 }
2296
2297 // put buffers back to the kernel
2298 try buf_grp.put(cqe1);
2299 try buf_grp.put(cqe2);
2300
2301 // restart multishot
2302 recv_user_data = rnd.int(u64);
2303 _ = try buf_grp.recv_multishot(recv_user_data, fds.server, 0);
2304 try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit
2305
2306 chunk = data[buffer_size * 2 .. buffer_size * 3]; // third chunk
2307 const cqe3 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
2308 try testing.expect(cqe3.flags & linux.IORING_CQE_F_MORE > 0);
2309 try buf_grp.put(cqe3);
2310
2311 chunk = data[buffer_size * 3 ..]; // last chunk
2312 const cqe4 = try expect_buf_grp_cqe(&ring, &buf_grp, recv_user_data, chunk);
2313 try testing.expect(cqe4.flags & linux.IORING_CQE_F_MORE > 0);
2314 try buf_grp.put(cqe4);
2315
2316 // cancel pending multishot recv operation
2317 {
2318 const cancel_user_data = rnd.int(u64);
2319 _ = try ring.cancel(cancel_user_data, recv_user_data, 0);
2320 try testing.expectEqual(@as(u32, 1), try ring.submit());
2321
2322 // expect completion of cancel operation and completion of recv operation
2323 var cqe_cancel = try ring.copy_cqe();
2324 if (cqe_cancel.err() == .INVAL) return error.SkipZigTest;
2325 var cqe_recv = try ring.copy_cqe();
2326 if (cqe_recv.err() == .INVAL) return error.SkipZigTest;
2327
2328 // don't depend on order of completions
2329 if (cqe_cancel.user_data == recv_user_data and cqe_recv.user_data == cancel_user_data) {
2330 const a = cqe_cancel;
2331 const b = cqe_recv;
2332 cqe_cancel = b;
2333 cqe_recv = a;
2334 }
2335
2336 // Note on different kernel results:
2337 // on older kernel (tested with v6.0.16, v6.1.57, v6.2.12, v6.4.16)
2338 // cqe_cancel.err() == .NOENT
2339 // cqe_recv.err() == .NOBUFS
2340 // on kernel (tested with v6.5.0, v6.5.7)
2341 // cqe_cancel.err() == .SUCCESS
2342 // cqe_recv.err() == .CANCELED
2343 // Upstream reference: https://github.com/axboe/liburing/issues/984
2344
2345 // cancel operation is success (or NOENT on older kernels)
2346 try testing.expectEqual(cancel_user_data, cqe_cancel.user_data);
2347 try testing.expect(cqe_cancel.err() == .NOENT or cqe_cancel.err() == .SUCCESS);
2348
2349 // recv operation is failed with err CANCELED (or NOBUFS on older kernels)
2350 try testing.expectEqual(recv_user_data, cqe_recv.user_data);
2351 try testing.expect(cqe_recv.res < 0);
2352 try testing.expect(cqe_recv.err() == .NOBUFS or cqe_recv.err() == .CANCELED);
2353 try testing.expect(cqe_recv.flags & linux.IORING_CQE_F_MORE == 0);
2354 }
2355 }
2356}
2357
2358test "copy_cqes with wrapping sq.cqes buffer" {
2359 var ring = IoUring.init(2, 0) catch |err| switch (err) {
2360 error.SystemOutdated => return error.SkipZigTest,
2361 error.PermissionDenied => return error.SkipZigTest,
2362 else => return err,
2363 };
2364 defer ring.deinit();
2365
2366 try testing.expectEqual(2, ring.sq.sqes.len);
2367 try testing.expectEqual(4, ring.cq.cqes.len);
2368
2369 // submit 2 entries, receive 2 completions
2370 var cqes: [8]linux.io_uring_cqe = undefined;
2371 {
2372 for (0..2) |_| {
2373 const sqe = try ring.get_sqe();
2374 sqe.prep_timeout(&.{ .sec = 0, .nsec = 10000 }, 0, 0);
2375 try testing.expect(try ring.submit() == 1);
2376 }
2377 var cqe_count: u32 = 0;
2378 while (cqe_count < 2) {
2379 cqe_count += try ring.copy_cqes(&cqes, 2 - cqe_count);
2380 }
2381 }
2382
2383 try testing.expectEqual(2, ring.cq.head.*);
2384
2385 // sq.sqes len is 4, starting at position 2
2386 // every 4 entries submit wraps completion buffer
2387 // we are reading ring.cq.cqes at indexes 2,3,0,1
2388 for (1..1024) |i| {
2389 for (0..4) |_| {
2390 const sqe = try ring.get_sqe();
2391 sqe.prep_timeout(&.{ .sec = 0, .nsec = 10000 }, 0, 0);
2392 try testing.expect(try ring.submit() == 1);
2393 }
2394 var cqe_count: u32 = 0;
2395 while (cqe_count < 4) {
2396 cqe_count += try ring.copy_cqes(&cqes, 4 - cqe_count);
2397 }
2398 try testing.expectEqual(4, cqe_count);
2399 try testing.expectEqual(2 + 4 * i, ring.cq.head.*);
2400 }
2401}
2402
2403test "bind/listen/connect" {
2404 if (builtin.cpu.arch == .s390x) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25956
2405
2406 var ring = IoUring.init(4, 0) catch |err| switch (err) {
2407 error.SystemOutdated => return error.SkipZigTest,
2408 error.PermissionDenied => return error.SkipZigTest,
2409 else => return err,
2410 };
2411 defer ring.deinit();
2412
2413 const probe = ring.get_probe() catch return error.SkipZigTest;
2414 // LISTEN is higher required operation
2415 if (!probe.is_supported(.LISTEN)) return error.SkipZigTest;
2416
2417 var addr: linux.sockaddr.in = .{
2418 .port = 0,
2419 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
2420 };
2421 const proto: u32 = if (addr.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP;
2422
2423 const listen_fd = brk: {
2424 // Create socket
2425 _ = try ring.socket(1, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
2426 try testing.expectEqual(1, try ring.submit());
2427 var cqe = try ring.copy_cqe();
2428 try testing.expectEqual(1, cqe.user_data);
2429 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2430 const listen_fd: linux.fd_t = @intCast(cqe.res);
2431 try testing.expect(listen_fd > 2);
2432
2433 // Prepare: set socket option * 2, bind, listen
2434 var optval: u32 = 1;
2435 (try ring.setsockopt(2, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval))).link_next();
2436 (try ring.setsockopt(3, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEPORT, mem.asBytes(&optval))).link_next();
2437 (try ring.bind(4, listen_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in), 0)).link_next();
2438 _ = try ring.listen(5, listen_fd, 1, 0);
2439 // Submit 4 operations
2440 try testing.expectEqual(4, try ring.submit());
2441 // Expect all to succeed
2442 for (2..6) |user_data| {
2443 cqe = try ring.copy_cqe();
2444 try testing.expectEqual(user_data, cqe.user_data);
2445 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2446 }
2447
2448 // Check that socket option is set
2449 optval = 0;
2450 _ = try ring.getsockopt(5, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval));
2451 try testing.expectEqual(1, try ring.submit());
2452 cqe = try ring.copy_cqe();
2453 try testing.expectEqual(5, cqe.user_data);
2454 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2455 try testing.expectEqual(1, optval);
2456
2457 // Read system assigned port into addr
2458 var addr_len: posix.socklen_t = @sizeOf(linux.sockaddr.in);
2459 try posix.getsockname(listen_fd, addrAny(&addr), &addr_len);
2460
2461 break :brk listen_fd;
2462 };
2463
2464 const connect_fd = brk: {
2465 // Create connect socket
2466 _ = try ring.socket(6, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
2467 try testing.expectEqual(1, try ring.submit());
2468 const cqe = try ring.copy_cqe();
2469 try testing.expectEqual(6, cqe.user_data);
2470 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2471 // Get connect socket fd
2472 const connect_fd: linux.fd_t = @intCast(cqe.res);
2473 try testing.expect(connect_fd > 2 and connect_fd != listen_fd);
2474 break :brk connect_fd;
2475 };
2476
2477 // Prepare accept/connect operations
2478 _ = try ring.accept(7, listen_fd, null, null, 0);
2479 _ = try ring.connect(8, connect_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in));
2480 try testing.expectEqual(2, try ring.submit());
2481 // Get listener accepted socket
2482 var accept_fd: posix.socket_t = 0;
2483 for (0..2) |_| {
2484 const cqe = try ring.copy_cqe();
2485 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2486 if (cqe.user_data == 7) {
2487 accept_fd = @intCast(cqe.res);
2488 } else {
2489 try testing.expectEqual(8, cqe.user_data);
2490 }
2491 }
2492 try testing.expect(accept_fd > 2 and accept_fd != listen_fd and accept_fd != connect_fd);
2493
2494 // Communicate
2495 try testSendRecv(&ring, connect_fd, accept_fd);
2496 try testSendRecv(&ring, accept_fd, connect_fd);
2497
2498 // Shutdown and close all sockets
2499 for ([_]posix.socket_t{ connect_fd, accept_fd, listen_fd }) |fd| {
2500 (try ring.shutdown(9, fd, posix.SHUT.RDWR)).link_next();
2501 _ = try ring.close(10, fd);
2502 try testing.expectEqual(2, try ring.submit());
2503 for (0..2) |i| {
2504 const cqe = try ring.copy_cqe();
2505 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2506 try testing.expectEqual(9 + i, cqe.user_data);
2507 }
2508 }
2509}
2510
2511// Prepare, submit recv and get cqe using buffer group.
2512fn buf_grp_recv_submit_get_cqe(
2513 ring: *IoUring,
2514 buf_grp: *BufferGroup,
2515 fd: linux.fd_t,
2516 user_data: u64,
2517) !linux.io_uring_cqe {
2518 // prepare and submit recv
2519 const sqe = try buf_grp.recv(user_data, fd, 0);
2520 try testing.expect(sqe.flags & linux.IOSQE_BUFFER_SELECT == linux.IOSQE_BUFFER_SELECT);
2521 try testing.expect(sqe.buf_index == buf_grp.group_id);
2522 try testing.expectEqual(@as(u32, 1), try ring.submit()); // submit
2523 // get cqe, expect success
2524 const cqe = try ring.copy_cqe();
2525 try testing.expectEqual(user_data, cqe.user_data);
2526 try testing.expect(cqe.res >= 0); // success
2527 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2528 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set
2529
2530 return cqe;
2531}
2532
2533fn expect_buf_grp_cqe(
2534 ring: *IoUring,
2535 buf_grp: *BufferGroup,
2536 user_data: u64,
2537 expected: []const u8,
2538) !linux.io_uring_cqe {
2539 // get cqe
2540 const cqe = try ring.copy_cqe();
2541 try testing.expectEqual(user_data, cqe.user_data);
2542 try testing.expect(cqe.res >= 0); // success
2543 try testing.expect(cqe.flags & linux.IORING_CQE_F_BUFFER == linux.IORING_CQE_F_BUFFER); // IORING_CQE_F_BUFFER flag is set
2544 try testing.expectEqual(expected.len, @as(usize, @intCast(cqe.res)));
2545 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2546
2547 // get buffer from pool
2548 const buffer_id = try cqe.buffer_id();
2549 const len = @as(usize, @intCast(cqe.res));
2550 const buf = buf_grp.get_by_id(buffer_id)[0..len];
2551 try testing.expectEqualSlices(u8, expected, buf);
2552
2553 return cqe;
2554}
2555
2556fn testSendRecv(ring: *IoUring, send_fd: posix.socket_t, recv_fd: posix.socket_t) !void {
2557 const buffer_send = "0123456789abcdf" ** 10;
2558 var buffer_recv: [buffer_send.len * 2]u8 = undefined;
2559
2560 // 2 sends
2561 _ = try ring.send(1, send_fd, buffer_send, linux.MSG.WAITALL);
2562 _ = try ring.send(2, send_fd, buffer_send, linux.MSG.WAITALL);
2563 try testing.expectEqual(2, try ring.submit());
2564 for (0..2) |i| {
2565 const cqe = try ring.copy_cqe();
2566 try testing.expectEqual(1 + i, cqe.user_data);
2567 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2568 try testing.expectEqual(buffer_send.len, @as(usize, @intCast(cqe.res)));
2569 }
2570
2571 // receive
2572 var recv_len: usize = 0;
2573 while (recv_len < buffer_send.len * 2) {
2574 _ = try ring.recv(3, recv_fd, .{ .buffer = buffer_recv[recv_len..] }, 0);
2575 try testing.expectEqual(1, try ring.submit());
2576 const cqe = try ring.copy_cqe();
2577 try testing.expectEqual(3, cqe.user_data);
2578 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2579 recv_len += @intCast(cqe.res);
2580 }
2581
2582 // inspect recv buffer
2583 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]);
2584 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[buffer_send.len..]);
2585}
2586
2587/// Used for testing server/client interactions.
2588pub const SocketTestHarness = struct {
2589 listener: posix.socket_t,
2590 server: posix.socket_t,
2591 client: posix.socket_t,
2592
2593 pub fn close(self: SocketTestHarness) void {
2594 posix.close(self.client);
2595 posix.close(self.listener);
2596 }
2597};
2598
2599pub fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
2600 // Create a TCP server socket
2601 var address: linux.sockaddr.in = .{
2602 .port = 0,
2603 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
2604 };
2605 const listener_socket = try createListenerSocket(&address);
2606 errdefer posix.close(listener_socket);
2607
2608 // Submit 1 accept
2609 var accept_addr: posix.sockaddr = undefined;
2610 var accept_addr_len: posix.socklen_t = @sizeOf(@TypeOf(accept_addr));
2611 _ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0);
2612
2613 // Create a TCP client socket
2614 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
2615 errdefer posix.close(client);
2616 _ = try ring.connect(0xcccccccc, client, addrAny(&address), @sizeOf(linux.sockaddr.in));
2617
2618 try testing.expectEqual(@as(u32, 2), try ring.submit());
2619
2620 var cqe_accept = try ring.copy_cqe();
2621 if (cqe_accept.err() == .INVAL) return error.SkipZigTest;
2622 var cqe_connect = try ring.copy_cqe();
2623 if (cqe_connect.err() == .INVAL) return error.SkipZigTest;
2624
2625 // The accept/connect CQEs may arrive in any order, the connect CQE will sometimes come first:
2626 if (cqe_accept.user_data == 0xcccccccc and cqe_connect.user_data == 0xaaaaaaaa) {
2627 const a = cqe_accept;
2628 const b = cqe_connect;
2629 cqe_accept = b;
2630 cqe_connect = a;
2631 }
2632
2633 try testing.expectEqual(@as(u64, 0xaaaaaaaa), cqe_accept.user_data);
2634 if (cqe_accept.res <= 0) std.debug.print("\ncqe_accept.res={}\n", .{cqe_accept.res});
2635 try testing.expect(cqe_accept.res > 0);
2636 try testing.expectEqual(@as(u32, 0), cqe_accept.flags);
2637 try testing.expectEqual(linux.io_uring_cqe{
2638 .user_data = 0xcccccccc,
2639 .res = 0,
2640 .flags = 0,
2641 }, cqe_connect);
2642
2643 // All good
2644
2645 return SocketTestHarness{
2646 .listener = listener_socket,
2647 .server = cqe_accept.res,
2648 .client = client,
2649 };
2650}
2651
2652fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t {
2653 const kernel_backlog = 1;
2654 const listener_socket = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
2655 errdefer posix.close(listener_socket);
2656
2657 try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
2658 try posix.bind(listener_socket, addrAny(address), @sizeOf(linux.sockaddr.in));
2659 try posix.listen(listener_socket, kernel_backlog);
2660
2661 // set address to the OS-chosen IP/port.
2662 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
2663 try posix.getsockname(listener_socket, addrAny(address), &slen);
2664
2665 return listener_socket;
2666}
2667
2668/// For use in tests. Returns SkipZigTest if kernel version is less than required.
2669inline fn skipKernelLessThan(required: std.SemanticVersion) !void {
2670 var uts: linux.utsname = undefined;
2671 const res = linux.uname(&uts);
2672 switch (linux.errno(res)) {
2673 .SUCCESS => {},
2674 else => |errno| return posix.unexpectedErrno(errno),
2675 }
2676
2677 const release = mem.sliceTo(&uts.release, 0);
2678 // Strips potential extra, as kernel version might not be semver compliant, example "6.8.9-300.fc40.x86_64"
2679 const extra_index = std.mem.indexOfAny(u8, release, "-+");
2680 const stripped = release[0..(extra_index orelse release.len)];
2681 // Make sure the input don't rely on the extra we just stripped
2682 try testing.expect(required.pre == null and required.build == null);
2683
2684 var current = try std.SemanticVersion.parse(stripped);
2685 current.pre = null; // don't check pre field
2686 if (required.order(current) == .gt) return error.SkipZigTest;
2687}
2688
2689fn addrAny(addr: *linux.sockaddr.in) *linux.sockaddr {
2690 return @ptrCast(addr);
2691}
lib/std/os/linux/test.zig+15-17
......@@ -12,14 +12,16 @@ const fs = std.fs;
1212test "fallocate" {
1313 if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/30220
1414
15 const io = std.testing.io;
16
1517 var tmp = std.testing.tmpDir(.{});
1618 defer tmp.cleanup();
1719
1820 const path = "test_fallocate";
19 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
20 defer file.close();
21 const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .permissions = .fromMode(0o666) });
22 defer file.close(io);
2123
22 try expect((try file.stat()).size == 0);
24 try expect((try file.stat(io)).size == 0);
2325
2426 const len: i64 = 65536;
2527 switch (linux.errno(linux.fallocate(file.handle, 0, 0, len))) {
......@@ -29,7 +31,7 @@ test "fallocate" {
2931 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
3032 }
3133
32 try expect((try file.stat()).size == len);
34 try expect((try file.stat(io)).size == len);
3335}
3436
3537test "getpid" {
......@@ -77,12 +79,14 @@ test "timer" {
7779}
7880
7981test "statx" {
82 const io = std.testing.io;
83
8084 var tmp = std.testing.tmpDir(.{});
8185 defer tmp.cleanup();
8286
8387 const tmp_file_name = "just_a_temporary_file.txt";
84 var file = try tmp.dir.createFile(tmp_file_name, .{});
85 defer file.close();
88 var file = try tmp.dir.createFile(io, tmp_file_name, .{});
89 defer file.close(io);
8690
8791 var buf: linux.Statx = undefined;
8892 switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .BASIC_STATS, &buf))) {
......@@ -111,15 +115,17 @@ test "user and group ids" {
111115}
112116
113117test "fadvise" {
118 const io = std.testing.io;
119
114120 var tmp = std.testing.tmpDir(.{});
115121 defer tmp.cleanup();
116122
117123 const tmp_file_name = "temp_posix_fadvise.txt";
118 var file = try tmp.dir.createFile(tmp_file_name, .{});
119 defer file.close();
124 var file = try tmp.dir.createFile(io, tmp_file_name, .{});
125 defer file.close(io);
120126
121127 var buf: [2048]u8 = undefined;
122 try file.writeAll(&buf);
128 try file.writeStreamingAll(io, &buf);
123129
124130 const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL);
125131 try expectEqual(@as(usize, 0), ret);
......@@ -401,14 +407,6 @@ test "futex2_requeue" {
401407 try expectEqual(0, rc);
402408}
403409
404test "copy_file_range error" {
405 const fds = try std.posix.pipe();
406 defer std.posix.close(fds[0]);
407 defer std.posix.close(fds[1]);
408
409 try std.testing.expectError(error.InvalidArguments, linux.wrapped.copy_file_range(fds[0], null, fds[1], null, 1, 0));
410}
411
412410test {
413411 _ = linux.IoUring;
414412}
lib/std/os/uefi/protocol/file.zig-9
......@@ -163,15 +163,6 @@ pub const File = extern struct {
163163 }
164164 }
165165
166 fn getEndPos(self: *File) SeekError!u64 {
167 const start_pos = try self.getPosition();
168 // ignore error
169 defer self.setPosition(start_pos) catch {};
170
171 try self.setPosition(end_of_file);
172 return self.getPosition();
173 }
174
175166 pub fn setPosition(self: *File, position: u64) SeekError!void {
176167 switch (self._set_position(self, position)) {
177168 .success => {},
lib/std/os/windows.zig+20-237
......@@ -215,6 +215,10 @@ pub const FILE = struct {
215215 AccessFlags: ACCESS_MASK,
216216 };
217217
218 /// This is not separated into RENAME_INFORMATION and RENAME_INFORMATION_EX because
219 /// the only difference is the `Flags` type (BOOLEAN before _EX, ULONG in the _EX),
220 /// which doesn't affect the struct layout--the offset of RootDirectory is the same
221 /// regardless.
218222 pub const RENAME_INFORMATION = extern struct {
219223 Flags: FLAGS,
220224 RootDirectory: ?HANDLE,
......@@ -2310,17 +2314,15 @@ pub const OpenFileOptions = struct {
23102314 sa: ?*SECURITY_ATTRIBUTES = null,
23112315 share_access: FILE.SHARE = .VALID_FLAGS,
23122316 creation: FILE.CREATE_DISPOSITION,
2313 /// If true, tries to open path as a directory.
2314 /// Defaults to false.
2315 filter: Filter = .file_only,
2317 filter: Filter = .non_directory_only,
23162318 /// If false, tries to open path as a reparse point without dereferencing it.
23172319 /// Defaults to true.
23182320 follow_symlinks: bool = true,
23192321
23202322 pub const Filter = enum {
23212323 /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory.
2322 file_only,
2323 /// Causes `OpenFile` to return `error.NotDir` if the opened handle would be a file.
2324 non_directory_only,
2325 /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory.
23242326 dir_only,
23252327 /// `OpenFile` does not discriminate between opening files and directories.
23262328 any,
......@@ -2328,10 +2330,10 @@ pub const OpenFileOptions = struct {
23282330};
23292331
23302332pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {
2331 if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .file_only) {
2333 if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .non_directory_only) {
23322334 return error.IsDir;
23332335 }
2334 if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .file_only) {
2336 if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .non_directory_only) {
23352337 return error.IsDir;
23362338 }
23372339
......@@ -2366,7 +2368,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
23662368 options.creation,
23672369 .{
23682370 .DIRECTORY_FILE = options.filter == .dir_only,
2369 .NON_DIRECTORY_FILE = options.filter == .file_only,
2371 .NON_DIRECTORY_FILE = options.filter == .non_directory_only,
23702372 .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS,
23712373 .OPEN_REPARSE_POINT = !options.follow_symlinks,
23722374 },
......@@ -2828,149 +2830,6 @@ pub fn CloseHandle(hObject: HANDLE) void {
28282830 assert(ntdll.NtClose(hObject) == .SUCCESS);
28292831}
28302832
2831pub const ReadFileError = error{
2832 BrokenPipe,
2833 /// The specified network name is no longer available.
2834 ConnectionResetByPeer,
2835 Canceled,
2836 /// Unable to read file due to lock.
2837 LockViolation,
2838 /// Known to be possible when:
2839 /// - Unable to read from disconnected virtual com port (Windows)
2840 AccessDenied,
2841 NotOpenForReading,
2842 Unexpected,
2843};
2844
2845/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into
2846/// multiple non-atomic reads.
2847pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize {
2848 while (true) {
2849 const want_read_count: DWORD = @min(@as(DWORD, maxInt(DWORD)), buffer.len);
2850 var amt_read: DWORD = undefined;
2851 var overlapped_data: OVERLAPPED = undefined;
2852 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
2853 overlapped_data = .{
2854 .Internal = 0,
2855 .InternalHigh = 0,
2856 .DUMMYUNIONNAME = .{
2857 .DUMMYSTRUCTNAME = .{
2858 .Offset = @as(u32, @truncate(off)),
2859 .OffsetHigh = @as(u32, @truncate(off >> 32)),
2860 },
2861 },
2862 .hEvent = null,
2863 };
2864 break :blk &overlapped_data;
2865 } else null;
2866 if (kernel32.ReadFile(in_hFile, buffer.ptr, want_read_count, &amt_read, overlapped) == 0) {
2867 switch (GetLastError()) {
2868 .IO_PENDING => unreachable,
2869 .OPERATION_ABORTED => continue,
2870 .BROKEN_PIPE => return 0,
2871 .HANDLE_EOF => return 0,
2872 .NETNAME_DELETED => return error.ConnectionResetByPeer,
2873 .LOCK_VIOLATION => return error.LockViolation,
2874 .ACCESS_DENIED => return error.AccessDenied,
2875 .INVALID_HANDLE => return error.NotOpenForReading,
2876 else => |err| return unexpectedError(err),
2877 }
2878 }
2879 return amt_read;
2880 }
2881}
2882
2883pub const WriteFileError = error{
2884 SystemResources,
2885 Canceled,
2886 BrokenPipe,
2887 NotOpenForWriting,
2888 /// The process cannot access the file because another process has locked
2889 /// a portion of the file.
2890 LockViolation,
2891 /// The specified network name is no longer available.
2892 ConnectionResetByPeer,
2893 /// Known to be possible when:
2894 /// - Unable to write to disconnected virtual com port (Windows)
2895 AccessDenied,
2896 Unexpected,
2897};
2898
2899pub fn WriteFile(
2900 handle: HANDLE,
2901 bytes: []const u8,
2902 offset: ?u64,
2903) WriteFileError!usize {
2904 var bytes_written: DWORD = undefined;
2905 var overlapped_data: OVERLAPPED = undefined;
2906 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
2907 overlapped_data = .{
2908 .Internal = 0,
2909 .InternalHigh = 0,
2910 .DUMMYUNIONNAME = .{
2911 .DUMMYSTRUCTNAME = .{
2912 .Offset = @truncate(off),
2913 .OffsetHigh = @truncate(off >> 32),
2914 },
2915 },
2916 .hEvent = null,
2917 };
2918 break :blk &overlapped_data;
2919 } else null;
2920 const adjusted_len = math.cast(u32, bytes.len) orelse maxInt(u32);
2921 if (kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, overlapped) == 0) {
2922 switch (GetLastError()) {
2923 .INVALID_USER_BUFFER => return error.SystemResources,
2924 .NOT_ENOUGH_MEMORY => return error.SystemResources,
2925 .OPERATION_ABORTED => return error.Canceled,
2926 .NOT_ENOUGH_QUOTA => return error.SystemResources,
2927 .IO_PENDING => unreachable,
2928 .NO_DATA => return error.BrokenPipe,
2929 .INVALID_HANDLE => return error.NotOpenForWriting,
2930 .LOCK_VIOLATION => return error.LockViolation,
2931 .NETNAME_DELETED => return error.ConnectionResetByPeer,
2932 .ACCESS_DENIED => return error.AccessDenied,
2933 .WORKING_SET_QUOTA => return error.SystemResources,
2934 else => |err| return unexpectedError(err),
2935 }
2936 }
2937 return bytes_written;
2938}
2939
2940pub const SetCurrentDirectoryError = error{
2941 NameTooLong,
2942 FileNotFound,
2943 NotDir,
2944 AccessDenied,
2945 NoDevice,
2946 BadPathName,
2947 Unexpected,
2948};
2949
2950pub fn SetCurrentDirectory(path_name: []const u16) SetCurrentDirectoryError!void {
2951 const path_len_bytes = math.cast(u16, path_name.len * 2) orelse return error.NameTooLong;
2952
2953 var nt_name: UNICODE_STRING = .{
2954 .Length = path_len_bytes,
2955 .MaximumLength = path_len_bytes,
2956 .Buffer = @constCast(path_name.ptr),
2957 };
2958
2959 const rc = ntdll.RtlSetCurrentDirectory_U(&nt_name);
2960 switch (rc) {
2961 .SUCCESS => {},
2962 .OBJECT_NAME_INVALID => return error.BadPathName,
2963 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
2964 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
2965 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
2966 .INVALID_PARAMETER => unreachable,
2967 .ACCESS_DENIED => return error.AccessDenied,
2968 .OBJECT_PATH_SYNTAX_BAD => unreachable,
2969 .NOT_A_DIRECTORY => return error.NotDir,
2970 else => return unexpectedStatus(rc),
2971 }
2972}
2973
29742833pub const GetCurrentDirectoryError = error{
29752834 NameTooLong,
29762835 Unexpected,
......@@ -3040,7 +2899,7 @@ pub fn CreateSymbolicLink(
30402899 },
30412900 .dir = dir,
30422901 .creation = .CREATE,
3043 .filter = if (is_directory) .dir_only else .file_only,
2902 .filter = if (is_directory) .dir_only else .non_directory_only,
30442903 }) catch |err| switch (err) {
30452904 error.IsDir => return error.PathAlreadyExists,
30462905 error.NotDir => return error.Unexpected,
......@@ -3584,7 +3443,7 @@ test QueryObjectName {
35843443 //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths.
35853444 var tmp = std.testing.tmpDir(.{});
35863445 defer tmp.cleanup();
3587 const handle = tmp.dir.fd;
3446 const handle = tmp.dir.handle;
35883447 var out_buffer: [PATH_MAX_WIDE]u16 = undefined;
35893448
35903449 const result_path = try QueryObjectName(handle, &out_buffer);
......@@ -3597,7 +3456,6 @@ test QueryObjectName {
35973456
35983457pub const GetFinalPathNameByHandleError = error{
35993458 AccessDenied,
3600 BadPathName,
36013459 FileNotFound,
36023460 NameTooLong,
36033461 /// The volume does not contain a recognized file system. File system
......@@ -3622,6 +3480,8 @@ pub const GetFinalPathNameByHandleFormat = struct {
36223480/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`).
36233481/// If DOS volume name format is selected, note that this function does *not* prepend
36243482/// `\\?\` prefix to the resultant path.
3483///
3484/// TODO move this function into std.Io.Threaded and add cancelation checks
36253485pub fn GetFinalPathNameByHandle(
36263486 hFile: HANDLE,
36273487 fmt: GetFinalPathNameByHandleFormat,
......@@ -3701,6 +3561,7 @@ pub fn GetFinalPathNameByHandle(
37013561 error.WouldBlock => return error.Unexpected,
37023562 error.NetworkNotFound => return error.Unexpected,
37033563 error.AntivirusInterference => return error.Unexpected,
3564 error.BadPathName => return error.Unexpected,
37043565 else => |e| return e,
37053566 };
37063567 defer CloseHandle(mgmt_handle);
......@@ -3746,9 +3607,7 @@ pub fn GetFinalPathNameByHandle(
37463607 const total_len = drive_letter.len + file_name_u16.len;
37473608
37483609 // Validate that DOS does not contain any spurious nul bytes.
3749 if (mem.findScalar(u16, out_buffer[0..total_len], 0)) |_| {
3750 return error.BadPathName;
3751 }
3610 assert(mem.findScalar(u16, out_buffer[0..total_len], 0) == null);
37523611
37533612 return out_buffer[0..total_len];
37543613 } else if (mountmgrIsVolumeName(symlink)) {
......@@ -3798,9 +3657,7 @@ pub fn GetFinalPathNameByHandle(
37983657 const total_len = volume_path.len + file_name_u16.len;
37993658
38003659 // Validate that DOS does not contain any spurious nul bytes.
3801 if (mem.findScalar(u16, out_buffer[0..total_len], 0)) |_| {
3802 return error.BadPathName;
3803 }
3660 assert(mem.findScalar(u16, out_buffer[0..total_len], 0) == null);
38043661
38053662 return out_buffer[0..total_len];
38063663 }
......@@ -3847,7 +3704,7 @@ test GetFinalPathNameByHandle {
38473704 //any file will do
38483705 var tmp = std.testing.tmpDir(.{});
38493706 defer tmp.cleanup();
3850 const handle = tmp.dir.fd;
3707 const handle = tmp.dir.handle;
38513708 var buffer: [PATH_MAX_WIDE]u16 = undefined;
38523709
38533710 //check with sufficient size
......@@ -4248,80 +4105,6 @@ pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter
42484105 assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0);
42494106}
42504107
4251pub const SetFileTimeError = error{Unexpected};
4252
4253pub fn SetFileTime(
4254 hFile: HANDLE,
4255 lpCreationTime: ?*const FILETIME,
4256 lpLastAccessTime: ?*const FILETIME,
4257 lpLastWriteTime: ?*const FILETIME,
4258) SetFileTimeError!void {
4259 const rc = kernel32.SetFileTime(hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime);
4260 if (rc == 0) {
4261 switch (GetLastError()) {
4262 else => |err| return unexpectedError(err),
4263 }
4264 }
4265}
4266
4267pub const LockFileError = error{
4268 SystemResources,
4269 WouldBlock,
4270} || UnexpectedError;
4271
4272pub fn LockFile(
4273 FileHandle: HANDLE,
4274 Event: ?HANDLE,
4275 ApcRoutine: ?*const IO_APC_ROUTINE,
4276 ApcContext: ?*anyopaque,
4277 IoStatusBlock: *IO_STATUS_BLOCK,
4278 ByteOffset: *const LARGE_INTEGER,
4279 Length: *const LARGE_INTEGER,
4280 Key: ?*ULONG,
4281 FailImmediately: BOOLEAN,
4282 ExclusiveLock: BOOLEAN,
4283) !void {
4284 const rc = ntdll.NtLockFile(
4285 FileHandle,
4286 Event,
4287 ApcRoutine,
4288 ApcContext,
4289 IoStatusBlock,
4290 ByteOffset,
4291 Length,
4292 Key,
4293 FailImmediately,
4294 ExclusiveLock,
4295 );
4296 switch (rc) {
4297 .SUCCESS => return,
4298 .INSUFFICIENT_RESOURCES => return error.SystemResources,
4299 .LOCK_NOT_GRANTED => return error.WouldBlock,
4300 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
4301 else => return unexpectedStatus(rc),
4302 }
4303}
4304
4305pub const UnlockFileError = error{
4306 RangeNotLocked,
4307} || UnexpectedError;
4308
4309pub fn UnlockFile(
4310 FileHandle: HANDLE,
4311 IoStatusBlock: *IO_STATUS_BLOCK,
4312 ByteOffset: *const LARGE_INTEGER,
4313 Length: *const LARGE_INTEGER,
4314 Key: ULONG,
4315) !void {
4316 const rc = ntdll.NtUnlockFile(FileHandle, IoStatusBlock, ByteOffset, Length, Key);
4317 switch (rc) {
4318 .SUCCESS => return,
4319 .RANGE_NOT_LOCKED => return error.RangeNotLocked,
4320 .ACCESS_VIOLATION => unreachable, // bad io_status_block pointer
4321 else => return unexpectedStatus(rc),
4322 }
4323}
4324
43254108/// This is a workaround for the C backend until zig has the ability to put
43264109/// C code in inline assembly.
43274110extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque;
......@@ -4713,8 +4496,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
47134496 break :path_to_get path;
47144497 }
47154498 // We can also skip GetFinalPathNameByHandle if the handle matches
4716 // the handle returned by fs.cwd()
4717 if (dir.? == std.fs.cwd().fd) {
4499 // the handle returned by Io.Dir.cwd()
4500 if (dir.? == Io.Dir.cwd().handle) {
47184501 break :path_to_get path;
47194502 }
47204503 // At this point, we know we have a relative path that had too many
lib/std/pdb.zig+1-1
......@@ -12,7 +12,7 @@ const math = std.math;
1212const mem = std.mem;
1313const coff = std.coff;
1414const fs = std.fs;
15const File = std.fs.File;
15const File = std.Io.File;
1616const debug = std.debug;
1717
1818const ArrayList = std.ArrayList;
lib/std/posix.zig+404-2954
......@@ -15,15 +15,16 @@
1515//! deal with the exception.
1616
1717const builtin = @import("builtin");
18const root = @import("root");
18const native_os = builtin.os.tag;
19
1920const std = @import("std.zig");
21const Io = std.Io;
2022const mem = std.mem;
2123const fs = std.fs;
22const max_path_bytes = fs.max_path_bytes;
24const max_path_bytes = std.fs.max_path_bytes;
2325const maxInt = std.math.maxInt;
2426const cast = std.math.cast;
2527const assert = std.debug.assert;
26const native_os = builtin.os.tag;
2728const page_size_min = std.heap.page_size_min;
2829
2930test {
......@@ -53,6 +54,7 @@ else switch (native_os) {
5354 pub const uid_t = void;
5455 pub const gid_t = void;
5556 pub const mode_t = u0;
57 pub const nlink_t = u0;
5658 pub const ino_t = void;
5759 pub const IFNAMESIZE = {};
5860 pub const SIG = void;
......@@ -309,274 +311,6 @@ pub fn close(fd: fd_t) void {
309311 }
310312}
311313
312pub const FChmodError = error{
313 AccessDenied,
314 PermissionDenied,
315 InputOutput,
316 SymLinkLoop,
317 FileNotFound,
318 SystemResources,
319 ReadOnlyFileSystem,
320} || UnexpectedError;
321
322/// Changes the mode of the file referred to by the file descriptor.
323///
324/// The process must have the correct privileges in order to do this
325/// successfully, or must have the effective user ID matching the owner
326/// of the file.
327pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
328 if (!fs.has_executable_bit) @compileError("fchmod unsupported by target OS");
329
330 while (true) {
331 const res = system.fchmod(fd, mode);
332 switch (errno(res)) {
333 .SUCCESS => return,
334 .INTR => continue,
335 .BADF => unreachable,
336 .FAULT => unreachable,
337 .INVAL => unreachable,
338 .ACCES => return error.AccessDenied,
339 .IO => return error.InputOutput,
340 .LOOP => return error.SymLinkLoop,
341 .NOENT => return error.FileNotFound,
342 .NOMEM => return error.SystemResources,
343 .NOTDIR => return error.FileNotFound,
344 .PERM => return error.PermissionDenied,
345 .ROFS => return error.ReadOnlyFileSystem,
346 else => |err| return unexpectedErrno(err),
347 }
348 }
349}
350
351pub const FChmodAtError = FChmodError || error{
352 /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded
353 /// `PATH_MAX`.
354 NameTooLong,
355 /// `path` resolves to a symbolic link, and `AT.SYMLINK_NOFOLLOW` was set
356 /// in `flags`. This error only occurs on Linux, where changing the mode of
357 /// a symbolic link has no meaning and can cause undefined behaviour on
358 /// certain filesystems.
359 ///
360 /// The procfs fallback was used but procfs was not mounted.
361 OperationNotSupported,
362 /// The procfs fallback was used but the process exceeded its open file
363 /// limit.
364 ProcessFdQuotaExceeded,
365 /// The procfs fallback was used but the system exceeded it open file limit.
366 SystemFdQuotaExceeded,
367 Canceled,
368};
369
370/// Changes the `mode` of `path` relative to the directory referred to by
371/// `dirfd`. The process must have the correct privileges in order to do this
372/// successfully, or must have the effective user ID matching the owner of the
373/// file.
374///
375/// On Linux the `fchmodat2` syscall will be used if available, otherwise a
376/// workaround using procfs will be employed. Changing the mode of a symbolic
377/// link with `AT.SYMLINK_NOFOLLOW` set will also return
378/// `OperationNotSupported`, as:
379///
380/// 1. Permissions on the link are ignored when resolving its target.
381/// 2. This operation has been known to invoke undefined behaviour across
382/// different filesystems[1].
383///
384/// [1]: https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html.
385pub inline fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
386 if (!fs.has_executable_bit) @compileError("fchmodat unsupported by target OS");
387
388 // No special handling for linux is needed if we can use the libc fallback
389 // or `flags` is empty. Glibc only added the fallback in 2.32.
390 const skip_fchmodat_fallback = native_os != .linux or
391 (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })) or
392 flags == 0;
393
394 // This function is marked inline so that when flags is comptime-known,
395 // skip_fchmodat_fallback will be comptime-known true.
396 if (skip_fchmodat_fallback)
397 return fchmodat1(dirfd, path, mode, flags);
398
399 return fchmodat2(dirfd, path, mode, flags);
400}
401
402fn fchmodat1(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
403 const path_c = try toPosixPath(path);
404 while (true) {
405 const res = system.fchmodat(dirfd, &path_c, mode, flags);
406 switch (errno(res)) {
407 .SUCCESS => return,
408 .INTR => continue,
409 .BADF => unreachable,
410 .FAULT => unreachable,
411 .INVAL => unreachable,
412 .ACCES => return error.AccessDenied,
413 .IO => return error.InputOutput,
414 .LOOP => return error.SymLinkLoop,
415 .MFILE => return error.ProcessFdQuotaExceeded,
416 .NAMETOOLONG => return error.NameTooLong,
417 .NFILE => return error.SystemFdQuotaExceeded,
418 .NOENT => return error.FileNotFound,
419 .NOTDIR => return error.FileNotFound,
420 .NOMEM => return error.SystemResources,
421 .OPNOTSUPP => return error.OperationNotSupported,
422 .PERM => return error.PermissionDenied,
423 .ROFS => return error.ReadOnlyFileSystem,
424 else => |err| return unexpectedErrno(err),
425 }
426 }
427}
428
429fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
430 const global = struct {
431 var has_fchmodat2: bool = true;
432 };
433 const path_c = try toPosixPath(path);
434 const use_fchmodat2 = (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse false) and
435 @atomicLoad(bool, &global.has_fchmodat2, .monotonic);
436 while (use_fchmodat2) {
437 // Later on this should be changed to `system.fchmodat2`
438 // when the musl/glibc add a wrapper.
439 const res = linux.fchmodat2(dirfd, &path_c, mode, flags);
440 switch (linux.errno(res)) {
441 .SUCCESS => return,
442 .INTR => continue,
443 .BADF => unreachable,
444 .FAULT => unreachable,
445 .INVAL => unreachable,
446 .ACCES => return error.AccessDenied,
447 .IO => return error.InputOutput,
448 .LOOP => return error.SymLinkLoop,
449 .NOENT => return error.FileNotFound,
450 .NOMEM => return error.SystemResources,
451 .NOTDIR => return error.FileNotFound,
452 .OPNOTSUPP => return error.OperationNotSupported,
453 .PERM => return error.PermissionDenied,
454 .ROFS => return error.ReadOnlyFileSystem,
455
456 .NOSYS => {
457 @atomicStore(bool, &global.has_fchmodat2, false, .monotonic);
458 break;
459 },
460 else => |err| return unexpectedErrno(err),
461 }
462 }
463
464 // Fallback to changing permissions using procfs:
465 //
466 // 1. Open `path` as a `PATH` descriptor.
467 // 2. Stat the fd and check if it isn't a symbolic link.
468 // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`.
469 // 4. Pass the procfs path to `chmod` with the `mode`.
470 var pathfd: fd_t = undefined;
471 while (true) {
472 const rc = system.openat(dirfd, &path_c, .{ .PATH = true, .NOFOLLOW = true, .CLOEXEC = true }, @as(mode_t, 0));
473 switch (errno(rc)) {
474 .SUCCESS => {
475 pathfd = @intCast(rc);
476 break;
477 },
478 .INTR => continue,
479 .FAULT => unreachable,
480 .INVAL => unreachable,
481 .ACCES => return error.AccessDenied,
482 .PERM => return error.PermissionDenied,
483 .LOOP => return error.SymLinkLoop,
484 .MFILE => return error.ProcessFdQuotaExceeded,
485 .NAMETOOLONG => return error.NameTooLong,
486 .NFILE => return error.SystemFdQuotaExceeded,
487 .NOENT => return error.FileNotFound,
488 .NOMEM => return error.SystemResources,
489 else => |err| return unexpectedErrno(err),
490 }
491 }
492 defer close(pathfd);
493
494 const path_mode = if (linux.wrapped.statx(
495 pathfd,
496 "",
497 AT.EMPTY_PATH,
498 .{ .TYPE = true },
499 )) |stx| blk: {
500 assert(stx.mask.TYPE);
501 break :blk stx.mode;
502 } else |err| switch (err) {
503 error.NameTooLong => unreachable,
504 error.FileNotFound => unreachable,
505 else => |e| return e,
506 };
507 // Even though we only wanted TYPE, the kernel can still fill in the additional bits.
508 if ((path_mode & S.IFMT) == S.IFLNK)
509 return error.OperationNotSupported;
510
511 var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
512 const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/fd/{d}", .{pathfd}, 0) catch unreachable;
513 while (true) {
514 const res = system.chmod(proc_path, mode);
515 switch (errno(res)) {
516 // Getting NOENT here means that procfs isn't mounted.
517 .NOENT => return error.OperationNotSupported,
518
519 .SUCCESS => return,
520 .INTR => continue,
521 .BADF => unreachable,
522 .FAULT => unreachable,
523 .INVAL => unreachable,
524 .ACCES => return error.AccessDenied,
525 .IO => return error.InputOutput,
526 .LOOP => return error.SymLinkLoop,
527 .NOMEM => return error.SystemResources,
528 .NOTDIR => return error.FileNotFound,
529 .PERM => return error.PermissionDenied,
530 .ROFS => return error.ReadOnlyFileSystem,
531 else => |err| return unexpectedErrno(err),
532 }
533 }
534}
535
536pub const FChownError = error{
537 AccessDenied,
538 PermissionDenied,
539 InputOutput,
540 SymLinkLoop,
541 FileNotFound,
542 SystemResources,
543 ReadOnlyFileSystem,
544} || UnexpectedError;
545
546/// Changes the owner and group of the file referred to by the file descriptor.
547/// The process must have the correct privileges in order to do this
548/// successfully. The group may be changed by the owner of the directory to
549/// any group of which the owner is a member. If the owner or group is
550/// specified as `null`, the ID is not changed.
551pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void {
552 switch (native_os) {
553 .windows, .wasi => @compileError("Unsupported OS"),
554 else => {},
555 }
556
557 while (true) {
558 const res = system.fchown(fd, owner orelse ~@as(uid_t, 0), group orelse ~@as(gid_t, 0));
559
560 switch (errno(res)) {
561 .SUCCESS => return,
562 .INTR => continue,
563 .BADF => unreachable, // Can be reached if the fd refers to a directory opened without `Dir.OpenOptions{ .iterate = true }`
564
565 .FAULT => unreachable,
566 .INVAL => unreachable,
567 .ACCES => return error.AccessDenied,
568 .IO => return error.InputOutput,
569 .LOOP => return error.SymLinkLoop,
570 .NOENT => return error.FileNotFound,
571 .NOMEM => return error.SystemResources,
572 .NOTDIR => return error.FileNotFound,
573 .PERM => return error.PermissionDenied,
574 .ROFS => return error.ReadOnlyFileSystem,
575 else => |err| return unexpectedErrno(err),
576 }
577 }
578}
579
580314pub const RebootError = error{
581315 PermissionDenied,
582316} || UnexpectedError;
......@@ -698,66 +432,6 @@ fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void {
698432 }
699433}
700434
701/// Causes abnormal process termination.
702/// If linking against libc, this calls the abort() libc function. Otherwise
703/// it raises SIGABRT followed by SIGKILL and finally lo
704/// Invokes the current signal handler for SIGABRT, if any.
705pub fn abort() noreturn {
706 @branchHint(.cold);
707 // MSVCRT abort() sometimes opens a popup window which is undesirable, so
708 // even when linking libc on Windows we use our own abort implementation.
709 // See https://github.com/ziglang/zig/issues/2071 for more details.
710 if (native_os == .windows) {
711 if (builtin.mode == .Debug and windows.peb().BeingDebugged != 0) {
712 @breakpoint();
713 }
714 windows.ntdll.RtlExitUserProcess(3);
715 }
716 if (!builtin.link_libc and native_os == .linux) {
717 // The Linux man page says that the libc abort() function
718 // "first unblocks the SIGABRT signal", but this is a footgun
719 // for user-defined signal handlers that want to restore some state in
720 // some program sections and crash in others.
721 // So, the user-installed SIGABRT handler is run, if present.
722 raise(.ABRT) catch {};
723
724 // Disable all signal handlers.
725 const filledset = linux.sigfillset();
726 sigprocmask(SIG.BLOCK, &filledset, null);
727
728 // Only one thread may proceed to the rest of abort().
729 if (!builtin.single_threaded) {
730 const global = struct {
731 var abort_entered: bool = false;
732 };
733 while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .seq_cst, .seq_cst)) |_| {}
734 }
735
736 // Install default handler so that the tkill below will terminate.
737 const sigact = Sigaction{
738 .handler = .{ .handler = SIG.DFL },
739 .mask = sigemptyset(),
740 .flags = 0,
741 };
742 sigaction(.ABRT, &sigact, null);
743
744 _ = linux.tkill(linux.gettid(), .ABRT);
745
746 var sigabrtmask = sigemptyset();
747 sigaddset(&sigabrtmask, .ABRT);
748 sigprocmask(SIG.UNBLOCK, &sigabrtmask, null);
749
750 // Beyond this point should be unreachable.
751 @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0;
752 raise(.KILL) catch {};
753 exit(127); // Pid 1 might not be signalled in some containers.
754 }
755 switch (native_os) {
756 .uefi, .wasi, .emscripten, .cuda, .amdhsa => @trap(),
757 else => system.abort(),
758 }
759}
760
761435pub const RaiseError = UnexpectedError;
762436
763437pub fn raise(sig: SIG) RaiseError!void {
......@@ -798,33 +472,6 @@ pub fn kill(pid: pid_t, sig: SIG) KillError!void {
798472 }
799473}
800474
801/// Exits all threads of the program with the specified status code.
802pub fn exit(status: u8) noreturn {
803 if (builtin.link_libc) {
804 std.c.exit(status);
805 }
806 if (native_os == .windows) {
807 windows.ntdll.RtlExitUserProcess(status);
808 }
809 if (native_os == .wasi) {
810 wasi.proc_exit(status);
811 }
812 if (native_os == .linux and !builtin.single_threaded) {
813 linux.exit_group(status);
814 }
815 if (native_os == .uefi) {
816 const uefi = std.os.uefi;
817 // exit() is only available if exitBootServices() has not been called yet.
818 // This call to exit should not fail, so we catch-ignore errors.
819 if (uefi.system_table.boot_services) |bs| {
820 bs.exit(uefi.handle, @enumFromInt(status), null) catch {};
821 }
822 // If we can't exit, reboot the system instead.
823 uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), null);
824 }
825 system.exit(status);
826}
827
828475pub const ReadError = std.Io.File.Reader.Error;
829476
830477/// Returns the number of bytes that were read, which can be less than
......@@ -839,34 +486,8 @@ pub const ReadError = std.Io.File.Reader.Error;
839486/// The corresponding POSIX limit is `maxInt(isize)`.
840487pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
841488 if (buf.len == 0) return 0;
842 if (native_os == .windows) {
843 return windows.ReadFile(fd, buf, null);
844 }
845 if (native_os == .wasi and !builtin.link_libc) {
846 const iovs = [1]iovec{iovec{
847 .base = buf.ptr,
848 .len = buf.len,
849 }};
850
851 var nread: usize = undefined;
852 switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) {
853 .SUCCESS => return nread,
854 .INTR => unreachable,
855 .INVAL => unreachable,
856 .FAULT => unreachable,
857 .AGAIN => unreachable,
858 .BADF => return error.NotOpenForReading, // Can be a race condition.
859 .IO => return error.InputOutput,
860 .ISDIR => return error.IsDir,
861 .NOBUFS => return error.SystemResources,
862 .NOMEM => return error.SystemResources,
863 .NOTCONN => return error.SocketUnconnected,
864 .CONNRESET => return error.ConnectionResetByPeer,
865 .TIMEDOUT => return error.Timeout,
866 .NOTCAPABLE => return error.AccessDenied,
867 else => |err| return unexpectedErrno(err),
868 }
869 }
489 if (native_os == .windows) @compileError("unsupported OS");
490 if (native_os == .wasi) @compileError("unsupported OS");
870491
871492 // Prevents EINVAL.
872493 const max_count = switch (native_os) {
......@@ -881,7 +502,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
881502 .INTR => continue,
882503 .INVAL => unreachable,
883504 .FAULT => unreachable,
884 .SRCH => return error.ProcessNotFound,
885505 .AGAIN => return error.WouldBlock,
886506 .CANCELED => return error.Canceled,
887507 .BADF => return error.NotOpenForReading, // Can be a race condition.
......@@ -897,1716 +517,472 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
897517 }
898518}
899519
900/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
520pub const WriteError = error{
521 DiskQuota,
522 FileTooBig,
523 InputOutput,
524 NoSpaceLeft,
525 DeviceBusy,
526 InvalidArgument,
527
528 /// File descriptor does not hold the required rights to write to it.
529 AccessDenied,
530 PermissionDenied,
531 BrokenPipe,
532 SystemResources,
533 Canceled,
534 NotOpenForWriting,
535
536 /// The process cannot access the file because another process has locked
537 /// a portion of the file. Windows-only.
538 LockViolation,
539
540 /// This error occurs when no global event loop is configured,
541 /// and reading from the file descriptor would block.
542 WouldBlock,
543
544 /// Connection reset by peer.
545 ConnectionResetByPeer,
546
547 /// This error occurs in Linux if the process being written to
548 /// no longer exists.
549 ProcessNotFound,
550 /// This error occurs when a device gets disconnected before or mid-flush
551 /// while it's being written to - errno(6): No such device or address.
552 NoDevice,
553
554 /// The socket type requires that message be sent atomically, and the size of the message
555 /// to be sent made this impossible. The message is not transmitted.
556 MessageOversize,
557} || UnexpectedError;
558
559/// Write to a file descriptor.
560/// Retries when interrupted by a signal.
561/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
562///
563/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
564/// occur for various reasons; for example, because there was insufficient space on the disk
565/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
566/// similar was interrupted by a signal handler after it had transferred some, but before it had
567/// transferred all of the requested bytes. In the event of a partial write, the caller can make
568/// another write() call to transfer the remaining bytes. The subsequent call will either
569/// transfer further bytes or may result in an error (e.g., if the disk is now full).
901570///
902571/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
903572/// return error.WouldBlock when EAGAIN is received.
904573/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
905574/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
906575///
907/// This operation is non-atomic on the following systems:
908/// * Windows
909/// On these systems, the read races with concurrent writes to the same file descriptor.
910///
911/// This function assumes that all vectors, including zero-length vectors, have
912/// a pointer within the address space of the application.
913pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
914 if (native_os == .windows) {
915 if (iov.len == 0) return 0;
916 const first = iov[0];
917 return read(fd, first.base[0..first.len]);
918 }
919 if (native_os == .wasi and !builtin.link_libc) {
920 var nread: usize = undefined;
921 switch (wasi.fd_read(fd, iov.ptr, iov.len, &nread)) {
922 .SUCCESS => return nread,
923 .INTR => unreachable,
924 .INVAL => unreachable,
925 .FAULT => unreachable,
926 .AGAIN => unreachable, // currently not support in WASI
927 .BADF => return error.NotOpenForReading, // can be a race condition
928 .IO => return error.InputOutput,
929 .ISDIR => return error.IsDir,
930 .NOBUFS => return error.SystemResources,
931 .NOMEM => return error.SystemResources,
932 .NOTCONN => return error.SocketUnconnected,
933 .CONNRESET => return error.ConnectionResetByPeer,
934 .TIMEDOUT => return error.Timeout,
935 .NOTCAPABLE => return error.AccessDenied,
936 else => |err| return unexpectedErrno(err),
937 }
938 }
576/// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000`
577/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
578/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
579/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
580/// The corresponding POSIX limit is `maxInt(isize)`.
581pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
582 if (bytes.len == 0) return 0;
583 if (native_os == .windows) @compileError("unsupported OS");
584 if (native_os == .wasi) @compileError("unsupported OS");
939585
586 const max_count = switch (native_os) {
587 .linux => 0x7ffff000,
588 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32),
589 else => maxInt(isize),
590 };
940591 while (true) {
941 const rc = system.readv(fd, iov.ptr, @min(iov.len, IOV_MAX));
592 const rc = system.write(fd, bytes.ptr, @min(bytes.len, max_count));
942593 switch (errno(rc)) {
943594 .SUCCESS => return @intCast(rc),
944595 .INTR => continue,
945 .INVAL => unreachable,
596 .INVAL => return error.InvalidArgument,
946597 .FAULT => unreachable,
947 .SRCH => return error.ProcessNotFound,
948598 .AGAIN => return error.WouldBlock,
949 .BADF => return error.NotOpenForReading, // can be a race condition
599 .BADF => return error.NotOpenForWriting, // can be a race condition.
600 .DESTADDRREQ => unreachable, // `connect` was never called.
601 .DQUOT => return error.DiskQuota,
602 .FBIG => return error.FileTooBig,
950603 .IO => return error.InputOutput,
951 .ISDIR => return error.IsDir,
952 .NOBUFS => return error.SystemResources,
953 .NOMEM => return error.SystemResources,
954 .NOTCONN => return error.SocketUnconnected,
604 .NOSPC => return error.NoSpaceLeft,
605 .ACCES => return error.AccessDenied,
606 .PERM => return error.PermissionDenied,
607 .PIPE => return error.BrokenPipe,
955608 .CONNRESET => return error.ConnectionResetByPeer,
956 .TIMEDOUT => return error.Timeout,
609 .BUSY => return error.DeviceBusy,
610 .NXIO => return error.NoDevice,
611 .MSGSIZE => return error.MessageOversize,
957612 else => |err| return unexpectedErrno(err),
958613 }
959614 }
960615}
961616
962pub const PReadError = std.Io.File.ReadPositionalError;
617pub const OpenError = std.Io.File.OpenError || error{WouldBlock};
963618
964/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
965///
966/// Retries when interrupted by a signal.
967///
968/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
969/// return error.WouldBlock when EAGAIN is received.
970/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
971/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
972///
973/// Linux has a limit on how many bytes may be transferred in one `pread` call, which is `0x7ffff000`
974/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
975/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page.
976/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
977/// The corresponding POSIX limit is `maxInt(isize)`.
978pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
979 if (buf.len == 0) return 0;
619/// Open and possibly create a file. Keeps trying if it gets interrupted.
620/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
621/// On WASI, `file_path` should be encoded as valid UTF-8.
622/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
623/// See also `openZ`.
624pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
980625 if (native_os == .windows) {
981 return windows.ReadFile(fd, buf, offset);
982 }
983 if (native_os == .wasi and !builtin.link_libc) {
984 const iovs = [1]iovec{iovec{
985 .base = buf.ptr,
986 .len = buf.len,
987 }};
988
989 var nread: usize = undefined;
990 switch (wasi.fd_pread(fd, &iovs, iovs.len, offset, &nread)) {
991 .SUCCESS => return nread,
992 .INTR => unreachable,
993 .INVAL => unreachable,
994 .FAULT => unreachable,
995 .AGAIN => unreachable,
996 .BADF => return error.NotOpenForReading, // Can be a race condition.
997 .IO => return error.InputOutput,
998 .ISDIR => return error.IsDir,
999 .NOBUFS => return error.SystemResources,
1000 .NOMEM => return error.SystemResources,
1001 .NOTCONN => return error.SocketUnconnected,
1002 .CONNRESET => return error.ConnectionResetByPeer,
1003 .TIMEDOUT => return error.Timeout,
1004 .NXIO => return error.Unseekable,
1005 .SPIPE => return error.Unseekable,
1006 .OVERFLOW => return error.Unseekable,
1007 .NOTCAPABLE => return error.AccessDenied,
1008 else => |err| return unexpectedErrno(err),
1009 }
626 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
627 } else if (native_os == .wasi and !builtin.link_libc) {
628 return openat(AT.FDCWD, file_path, flags, perm);
1010629 }
630 const file_path_c = try toPosixPath(file_path);
631 return openZ(&file_path_c, flags, perm);
632}
1011633
1012 // Prevent EINVAL.
1013 const max_count = switch (native_os) {
1014 .linux => 0x7ffff000,
1015 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32),
1016 else => maxInt(isize),
1017 };
634/// Open and possibly create a file. Keeps trying if it gets interrupted.
635/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
636/// On WASI, `file_path` should be encoded as valid UTF-8.
637/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
638/// See also `open`.
639pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
640 if (native_os == .windows) {
641 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
642 } else if (native_os == .wasi and !builtin.link_libc) {
643 return open(mem.sliceTo(file_path, 0), flags, perm);
644 }
1018645
1019 const pread_sym = if (lfs64_abi) system.pread64 else system.pread;
646 const open_sym = if (lfs64_abi) system.open64 else system.open;
1020647 while (true) {
1021 const rc = pread_sym(fd, buf.ptr, @min(buf.len, max_count), @bitCast(offset));
648 const rc = open_sym(file_path, flags, perm);
1022649 switch (errno(rc)) {
1023650 .SUCCESS => return @intCast(rc),
1024651 .INTR => continue,
1025 .INVAL => unreachable,
652
1026653 .FAULT => unreachable,
1027 .SRCH => return error.ProcessNotFound,
1028 .AGAIN => return error.WouldBlock,
1029 .BADF => return error.NotOpenForReading, // Can be a race condition.
1030 .IO => return error.InputOutput,
654 .INVAL => return error.BadPathName,
655 .ACCES => return error.AccessDenied,
656 .FBIG => return error.FileTooBig,
657 .OVERFLOW => return error.FileTooBig,
1031658 .ISDIR => return error.IsDir,
1032 .NOBUFS => return error.SystemResources,
659 .LOOP => return error.SymLinkLoop,
660 .MFILE => return error.ProcessFdQuotaExceeded,
661 .NAMETOOLONG => return error.NameTooLong,
662 .NFILE => return error.SystemFdQuotaExceeded,
663 .NODEV => return error.NoDevice,
664 .NOENT => return error.FileNotFound,
665 // Can happen on Linux when opening procfs files.
666 .SRCH => return error.FileNotFound,
1033667 .NOMEM => return error.SystemResources,
1034 .NOTCONN => return error.SocketUnconnected,
1035 .CONNRESET => return error.ConnectionResetByPeer,
1036 .TIMEDOUT => return error.Timeout,
1037 .NXIO => return error.Unseekable,
1038 .SPIPE => return error.Unseekable,
1039 .OVERFLOW => return error.Unseekable,
668 .NOSPC => return error.NoSpaceLeft,
669 .NOTDIR => return error.NotDir,
670 .PERM => return error.PermissionDenied,
671 .EXIST => return error.PathAlreadyExists,
672 .BUSY => return error.DeviceBusy,
673 .ILSEQ => return error.BadPathName,
1040674 else => |err| return unexpectedErrno(err),
1041675 }
1042676 }
1043677}
1044678
1045pub const TruncateError = error{
1046 FileTooBig,
1047 InputOutput,
1048 FileBusy,
1049 AccessDenied,
1050 PermissionDenied,
1051 NonResizable,
1052} || UnexpectedError;
1053
1054/// Length must be positive when treated as an i64.
1055pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
1056 const signed_len: i64 = @bitCast(length);
1057 if (signed_len < 0) return error.FileTooBig; // avoid ambiguous EINVAL errors
1058
679/// Open and possibly create a file. Keeps trying if it gets interrupted.
680/// `file_path` is relative to the open directory handle `dir_fd`.
681/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
682/// On WASI, `file_path` should be encoded as valid UTF-8.
683/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
684/// See also `openatZ`.
685pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {
1059686 if (native_os == .windows) {
1060 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1061 const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{
1062 .EndOfFile = signed_len,
1063 };
1064 const rc = windows.ntdll.NtSetInformationFile(
1065 fd,
1066 &io_status_block,
1067 &eof_info,
1068 @sizeOf(windows.FILE.END_OF_FILE_INFORMATION),
1069 .EndOfFile,
1070 );
1071 switch (rc) {
1072 .SUCCESS => return,
1073 .INVALID_HANDLE => unreachable, // Handle not open for writing
1074 .ACCESS_DENIED => return error.AccessDenied,
1075 .USER_MAPPED_FILE => return error.AccessDenied,
1076 .INVALID_PARAMETER => return error.FileTooBig,
1077 else => return windows.unexpectedStatus(rc),
1078 }
687 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
688 } else if (native_os == .wasi and !builtin.link_libc) {
689 @compileError("use std.Io instead");
1079690 }
1080 if (native_os == .wasi and !builtin.link_libc) {
1081 switch (wasi.fd_filestat_set_size(fd, length)) {
1082 .SUCCESS => return,
1083 .INTR => unreachable,
1084 .FBIG => return error.FileTooBig,
1085 .IO => return error.InputOutput,
1086 .PERM => return error.PermissionDenied,
1087 .TXTBSY => return error.FileBusy,
1088 .BADF => unreachable, // Handle not open for writing
1089 .INVAL => return error.NonResizable,
1090 .NOTCAPABLE => return error.AccessDenied,
1091 else => |err| return unexpectedErrno(err),
1092 }
691 const file_path_c = try toPosixPath(file_path);
692 return openatZ(dir_fd, &file_path_c, flags, mode);
693}
694
695/// Open and possibly create a file. Keeps trying if it gets interrupted.
696/// `file_path` is relative to the open directory handle `dir_fd`.
697/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
698/// On WASI, `file_path` should be encoded as valid UTF-8.
699/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
700/// See also `openat`.
701pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {
702 if (native_os == .windows) {
703 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
704 } else if (native_os == .wasi and !builtin.link_libc) {
705 return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode);
1093706 }
1094707
1095 const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate;
708 const openat_sym = if (lfs64_abi) system.openat64 else system.openat;
1096709 while (true) {
1097 switch (errno(ftruncate_sym(fd, signed_len))) {
1098 .SUCCESS => return,
710 const rc = openat_sym(dir_fd, file_path, flags, mode);
711 switch (errno(rc)) {
712 .SUCCESS => return @intCast(rc),
1099713 .INTR => continue,
714
715 .FAULT => unreachable,
716 .INVAL => return error.BadPathName,
717 .BADF => unreachable,
718 .ACCES => return error.AccessDenied,
1100719 .FBIG => return error.FileTooBig,
1101 .IO => return error.InputOutput,
720 .OVERFLOW => return error.FileTooBig,
721 .ISDIR => return error.IsDir,
722 .LOOP => return error.SymLinkLoop,
723 .MFILE => return error.ProcessFdQuotaExceeded,
724 .NAMETOOLONG => return error.NameTooLong,
725 .NFILE => return error.SystemFdQuotaExceeded,
726 .NODEV => return error.NoDevice,
727 .NOENT => return error.FileNotFound,
728 .SRCH => return error.FileNotFound,
729 .NOMEM => return error.SystemResources,
730 .NOSPC => return error.NoSpaceLeft,
731 .NOTDIR => return error.NotDir,
1102732 .PERM => return error.PermissionDenied,
733 .EXIST => return error.PathAlreadyExists,
734 .BUSY => return error.DeviceBusy,
735 .OPNOTSUPP => return error.FileLocksUnsupported,
736 .AGAIN => return error.WouldBlock,
1103737 .TXTBSY => return error.FileBusy,
1104 .BADF => unreachable, // Handle not open for writing
1105 .INVAL => return error.NonResizable, // This is returned for /dev/null for example.
738 .NXIO => return error.NoDevice,
739 .ILSEQ => return error.BadPathName,
1106740 else => |err| return unexpectedErrno(err),
1107741 }
1108742 }
1109743}
1110744
1111/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
1112///
1113/// Retries when interrupted by a signal.
1114///
1115/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
1116/// return error.WouldBlock when EAGAIN is received.
1117/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
1118/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
1119///
1120/// This operation is non-atomic on the following systems:
1121/// * Darwin
1122/// * Windows
1123/// On these systems, the read races with concurrent writes to the same file descriptor.
1124pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
1125 const have_pread_but_not_preadv = switch (native_os) {
1126 .windows, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .haiku => true,
1127 else => false,
745pub fn dup(old_fd: fd_t) !fd_t {
746 const rc = system.dup(old_fd);
747 return switch (errno(rc)) {
748 .SUCCESS => return @intCast(rc),
749 .MFILE => error.ProcessFdQuotaExceeded,
750 .BADF => unreachable, // invalid file descriptor
751 else => |err| return unexpectedErrno(err),
1128752 };
1129 if (have_pread_but_not_preadv) {
1130 // We could loop here; but proper usage of `preadv` must handle partial reads anyway.
1131 // So we simply read into the first vector only.
1132 if (iov.len == 0) return 0;
1133 const first = iov[0];
1134 return pread(fd, first.base[0..first.len], offset);
1135 }
1136 if (native_os == .wasi and !builtin.link_libc) {
1137 var nread: usize = undefined;
1138 switch (wasi.fd_pread(fd, iov.ptr, iov.len, offset, &nread)) {
1139 .SUCCESS => return nread,
1140 .INTR => unreachable,
1141 .INVAL => unreachable,
1142 .FAULT => unreachable,
1143 .AGAIN => unreachable,
1144 .BADF => return error.NotOpenForReading, // can be a race condition
1145 .IO => return error.InputOutput,
1146 .ISDIR => return error.IsDir,
1147 .NOBUFS => return error.SystemResources,
1148 .NOMEM => return error.SystemResources,
1149 .NOTCONN => return error.SocketUnconnected,
1150 .CONNRESET => return error.ConnectionResetByPeer,
1151 .TIMEDOUT => return error.Timeout,
1152 .NXIO => return error.Unseekable,
1153 .SPIPE => return error.Unseekable,
1154 .OVERFLOW => return error.Unseekable,
1155 .NOTCAPABLE => return error.AccessDenied,
1156 else => |err| return unexpectedErrno(err),
1157 }
1158 }
753}
1159754
1160 const preadv_sym = if (lfs64_abi) system.preadv64 else system.preadv;
755pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
1161756 while (true) {
1162 const rc = preadv_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset));
1163 switch (errno(rc)) {
1164 .SUCCESS => return @bitCast(rc),
1165 .INTR => continue,
1166 .INVAL => unreachable,
1167 .FAULT => unreachable,
1168 .SRCH => return error.ProcessNotFound,
1169 .AGAIN => return error.WouldBlock,
1170 .BADF => return error.NotOpenForReading, // can be a race condition
1171 .IO => return error.InputOutput,
1172 .ISDIR => return error.IsDir,
1173 .NOBUFS => return error.SystemResources,
1174 .NOMEM => return error.SystemResources,
1175 .NOTCONN => return error.SocketUnconnected,
1176 .CONNRESET => return error.ConnectionResetByPeer,
1177 .TIMEDOUT => return error.Timeout,
1178 .NXIO => return error.Unseekable,
1179 .SPIPE => return error.Unseekable,
1180 .OVERFLOW => return error.Unseekable,
757 switch (errno(system.dup2(old_fd, new_fd))) {
758 .SUCCESS => return,
759 .BUSY, .INTR => continue,
760 .MFILE => return error.ProcessFdQuotaExceeded,
761 .INVAL => unreachable, // invalid parameters passed to dup2
762 .BADF => unreachable, // invalid file descriptor
1181763 else => |err| return unexpectedErrno(err),
1182764 }
1183765 }
1184766}
1185767
1186pub const WriteError = error{
1187 DiskQuota,
1188 FileTooBig,
1189 InputOutput,
1190 NoSpaceLeft,
1191 DeviceBusy,
1192 InvalidArgument,
1193
1194 /// File descriptor does not hold the required rights to write to it.
1195 AccessDenied,
1196 PermissionDenied,
1197 BrokenPipe,
1198 SystemResources,
1199 Canceled,
1200 NotOpenForWriting,
1201
1202 /// The process cannot access the file because another process has locked
1203 /// a portion of the file. Windows-only.
1204 LockViolation,
1205
1206 /// This error occurs when no global event loop is configured,
1207 /// and reading from the file descriptor would block.
1208 WouldBlock,
1209
1210 /// Connection reset by peer.
1211 ConnectionResetByPeer,
1212
1213 /// This error occurs in Linux if the process being written to
1214 /// no longer exists.
1215 ProcessNotFound,
1216 /// This error occurs when a device gets disconnected before or mid-flush
1217 /// while it's being written to - errno(6): No such device or address.
1218 NoDevice,
1219
1220 /// The socket type requires that message be sent atomically, and the size of the message
1221 /// to be sent made this impossible. The message is not transmitted.
1222 MessageOversize,
1223} || UnexpectedError;
1224
1225/// Write to a file descriptor.
1226/// Retries when interrupted by a signal.
1227/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
1228///
1229/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
1230/// occur for various reasons; for example, because there was insufficient space on the disk
1231/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
1232/// similar was interrupted by a signal handler after it had transferred some, but before it had
1233/// transferred all of the requested bytes. In the event of a partial write, the caller can make
1234/// another write() call to transfer the remaining bytes. The subsequent call will either
1235/// transfer further bytes or may result in an error (e.g., if the disk is now full).
1236///
1237/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
1238/// return error.WouldBlock when EAGAIN is received.
1239/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
1240/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
1241///
1242/// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000`
1243/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
1244/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
1245/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
1246/// The corresponding POSIX limit is `maxInt(isize)`.
1247pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
1248 if (bytes.len == 0) return 0;
1249 if (native_os == .windows) {
1250 return windows.WriteFile(fd, bytes, null);
1251 }
1252
1253 if (native_os == .wasi and !builtin.link_libc) {
1254 const ciovs = [_]iovec_const{iovec_const{
1255 .base = bytes.ptr,
1256 .len = bytes.len,
1257 }};
1258 var nwritten: usize = undefined;
1259 switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) {
1260 .SUCCESS => return nwritten,
1261 .INTR => unreachable,
1262 .INVAL => unreachable,
1263 .FAULT => unreachable,
1264 .AGAIN => unreachable,
1265 .BADF => return error.NotOpenForWriting, // can be a race condition.
1266 .DESTADDRREQ => unreachable, // `connect` was never called.
1267 .DQUOT => return error.DiskQuota,
1268 .FBIG => return error.FileTooBig,
1269 .IO => return error.InputOutput,
1270 .NOSPC => return error.NoSpaceLeft,
1271 .PERM => return error.PermissionDenied,
1272 .PIPE => return error.BrokenPipe,
1273 .NOTCAPABLE => return error.AccessDenied,
1274 else => |err| return unexpectedErrno(err),
1275 }
1276 }
1277
1278 const max_count = switch (native_os) {
1279 .linux => 0x7ffff000,
1280 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32),
1281 else => maxInt(isize),
1282 };
1283 while (true) {
1284 const rc = system.write(fd, bytes.ptr, @min(bytes.len, max_count));
1285 switch (errno(rc)) {
1286 .SUCCESS => return @intCast(rc),
1287 .INTR => continue,
1288 .INVAL => return error.InvalidArgument,
1289 .FAULT => unreachable,
1290 .SRCH => return error.ProcessNotFound,
1291 .AGAIN => return error.WouldBlock,
1292 .BADF => return error.NotOpenForWriting, // can be a race condition.
1293 .DESTADDRREQ => unreachable, // `connect` was never called.
1294 .DQUOT => return error.DiskQuota,
1295 .FBIG => return error.FileTooBig,
1296 .IO => return error.InputOutput,
1297 .NOSPC => return error.NoSpaceLeft,
1298 .ACCES => return error.AccessDenied,
1299 .PERM => return error.PermissionDenied,
1300 .PIPE => return error.BrokenPipe,
1301 .CONNRESET => return error.ConnectionResetByPeer,
1302 .BUSY => return error.DeviceBusy,
1303 .NXIO => return error.NoDevice,
1304 .MSGSIZE => return error.MessageOversize,
1305 else => |err| return unexpectedErrno(err),
1306 }
1307 }
1308}
1309
1310/// Write multiple buffers to a file descriptor.
1311/// Retries when interrupted by a signal.
1312/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
1313///
1314/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
1315/// occur for various reasons; for example, because there was insufficient space on the disk
1316/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
1317/// similar was interrupted by a signal handler after it had transferred some, but before it had
1318/// transferred all of the requested bytes. In the event of a partial write, the caller can make
1319/// another write() call to transfer the remaining bytes. The subsequent call will either
1320/// transfer further bytes or may result in an error (e.g., if the disk is now full).
1321///
1322/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
1323/// return error.WouldBlock when EAGAIN is received.
1324/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
1325/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
1326///
1327/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur.
1328///
1329/// This function assumes that all vectors, including zero-length vectors, have
1330/// a pointer within the address space of the application.
1331pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
1332 if (native_os == .windows) {
1333 // TODO improve this to use WriteFileScatter
1334 if (iov.len == 0) return 0;
1335 const first = iov[0];
1336 return write(fd, first.base[0..first.len]);
1337 }
1338 if (native_os == .wasi and !builtin.link_libc) {
1339 var nwritten: usize = undefined;
1340 switch (wasi.fd_write(fd, iov.ptr, iov.len, &nwritten)) {
1341 .SUCCESS => return nwritten,
1342 .INTR => unreachable,
1343 .INVAL => unreachable,
1344 .FAULT => unreachable,
1345 .AGAIN => unreachable,
1346 .BADF => return error.NotOpenForWriting, // can be a race condition.
1347 .DESTADDRREQ => unreachable, // `connect` was never called.
1348 .DQUOT => return error.DiskQuota,
1349 .FBIG => return error.FileTooBig,
1350 .IO => return error.InputOutput,
1351 .NOSPC => return error.NoSpaceLeft,
1352 .PERM => return error.PermissionDenied,
1353 .PIPE => return error.BrokenPipe,
1354 .NOTCAPABLE => return error.AccessDenied,
1355 else => |err| return unexpectedErrno(err),
1356 }
1357 }
1358
1359 while (true) {
1360 const rc = system.writev(fd, iov.ptr, @min(iov.len, IOV_MAX));
1361 switch (errno(rc)) {
1362 .SUCCESS => return @intCast(rc),
1363 .INTR => continue,
1364 .INVAL => return error.InvalidArgument,
1365 .FAULT => unreachable,
1366 .SRCH => return error.ProcessNotFound,
1367 .AGAIN => return error.WouldBlock,
1368 .BADF => return error.NotOpenForWriting, // Can be a race condition.
1369 .DESTADDRREQ => unreachable, // `connect` was never called.
1370 .DQUOT => return error.DiskQuota,
1371 .FBIG => return error.FileTooBig,
1372 .IO => return error.InputOutput,
1373 .NOSPC => return error.NoSpaceLeft,
1374 .PERM => return error.PermissionDenied,
1375 .PIPE => return error.BrokenPipe,
1376 .CONNRESET => return error.ConnectionResetByPeer,
1377 .BUSY => return error.DeviceBusy,
1378 .CANCELED => return error.Canceled,
1379 else => |err| return unexpectedErrno(err),
1380 }
1381 }
1382}
1383
1384pub const PWriteError = WriteError || error{Unseekable};
1385
1386/// Write to a file descriptor, with a position offset.
1387/// Retries when interrupted by a signal.
1388/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
1389///
1390/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
1391/// occur for various reasons; for example, because there was insufficient space on the disk
1392/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
1393/// similar was interrupted by a signal handler after it had transferred some, but before it had
1394/// transferred all of the requested bytes. In the event of a partial write, the caller can make
1395/// another write() call to transfer the remaining bytes. The subsequent call will either
1396/// transfer further bytes or may result in an error (e.g., if the disk is now full).
1397///
1398/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
1399/// return error.WouldBlock when EAGAIN is received.
1400/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
1401/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
1402///
1403/// Linux has a limit on how many bytes may be transferred in one `pwrite` call, which is `0x7ffff000`
1404/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
1405/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
1406/// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL.
1407/// The corresponding POSIX limit is `maxInt(isize)`.
1408pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
1409 if (bytes.len == 0) return 0;
1410 if (native_os == .windows) {
1411 return windows.WriteFile(fd, bytes, offset);
1412 }
1413 if (native_os == .wasi and !builtin.link_libc) {
1414 const ciovs = [1]iovec_const{iovec_const{
1415 .base = bytes.ptr,
1416 .len = bytes.len,
1417 }};
1418
1419 var nwritten: usize = undefined;
1420 switch (wasi.fd_pwrite(fd, &ciovs, ciovs.len, offset, &nwritten)) {
1421 .SUCCESS => return nwritten,
1422 .INTR => unreachable,
1423 .INVAL => unreachable,
1424 .FAULT => unreachable,
1425 .AGAIN => unreachable,
1426 .BADF => return error.NotOpenForWriting, // can be a race condition.
1427 .DESTADDRREQ => unreachable, // `connect` was never called.
1428 .DQUOT => return error.DiskQuota,
1429 .FBIG => return error.FileTooBig,
1430 .IO => return error.InputOutput,
1431 .NOSPC => return error.NoSpaceLeft,
1432 .PERM => return error.PermissionDenied,
1433 .PIPE => return error.BrokenPipe,
1434 .NXIO => return error.Unseekable,
1435 .SPIPE => return error.Unseekable,
1436 .OVERFLOW => return error.Unseekable,
1437 .NOTCAPABLE => return error.AccessDenied,
1438 else => |err| return unexpectedErrno(err),
1439 }
1440 }
1441
1442 // Prevent EINVAL.
1443 const max_count = switch (native_os) {
1444 .linux => 0x7ffff000,
1445 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32),
1446 else => maxInt(isize),
1447 };
1448
1449 const pwrite_sym = if (lfs64_abi) system.pwrite64 else system.pwrite;
1450 while (true) {
1451 const rc = pwrite_sym(fd, bytes.ptr, @min(bytes.len, max_count), @bitCast(offset));
1452 switch (errno(rc)) {
1453 .SUCCESS => return @intCast(rc),
1454 .INTR => continue,
1455 .INVAL => return error.InvalidArgument,
1456 .FAULT => unreachable,
1457 .SRCH => return error.ProcessNotFound,
1458 .AGAIN => return error.WouldBlock,
1459 .BADF => return error.NotOpenForWriting, // Can be a race condition.
1460 .DESTADDRREQ => unreachable, // `connect` was never called.
1461 .DQUOT => return error.DiskQuota,
1462 .FBIG => return error.FileTooBig,
1463 .IO => return error.InputOutput,
1464 .NOSPC => return error.NoSpaceLeft,
1465 .PERM => return error.PermissionDenied,
1466 .PIPE => return error.BrokenPipe,
1467 .NXIO => return error.Unseekable,
1468 .SPIPE => return error.Unseekable,
1469 .OVERFLOW => return error.Unseekable,
1470 .BUSY => return error.DeviceBusy,
1471 else => |err| return unexpectedErrno(err),
1472 }
1473 }
1474}
1475
1476/// Write multiple buffers to a file descriptor, with a position offset.
1477/// Retries when interrupted by a signal.
1478/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
1479///
1480/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
1481/// occur for various reasons; for example, because there was insufficient space on the disk
1482/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
1483/// similar was interrupted by a signal handler after it had transferred some, but before it had
1484/// transferred all of the requested bytes. In the event of a partial write, the caller can make
1485/// another write() call to transfer the remaining bytes. The subsequent call will either
1486/// transfer further bytes or may result in an error (e.g., if the disk is now full).
1487///
1488/// If `fd` is opened in non blocking mode, the function will
1489/// return error.WouldBlock when EAGAIN is received.
1490///
1491/// The following systems do not have this syscall, and will return partial writes if more than one
1492/// vector is provided:
1493/// * Darwin
1494/// * Windows
1495///
1496/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur.
1497pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize {
1498 const have_pwrite_but_not_pwritev = switch (native_os) {
1499 .windows, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .haiku => true,
1500 else => false,
1501 };
1502
1503 if (have_pwrite_but_not_pwritev) {
1504 // We could loop here; but proper usage of `pwritev` must handle partial writes anyway.
1505 // So we simply write the first vector only.
1506 if (iov.len == 0) return 0;
1507 const first = iov[0];
1508 return pwrite(fd, first.base[0..first.len], offset);
1509 }
1510 if (native_os == .wasi and !builtin.link_libc) {
1511 var nwritten: usize = undefined;
1512 switch (wasi.fd_pwrite(fd, iov.ptr, iov.len, offset, &nwritten)) {
1513 .SUCCESS => return nwritten,
1514 .INTR => unreachable,
1515 .INVAL => unreachable,
1516 .FAULT => unreachable,
1517 .AGAIN => unreachable,
1518 .BADF => return error.NotOpenForWriting, // Can be a race condition.
1519 .DESTADDRREQ => unreachable, // `connect` was never called.
1520 .DQUOT => return error.DiskQuota,
1521 .FBIG => return error.FileTooBig,
1522 .IO => return error.InputOutput,
1523 .NOSPC => return error.NoSpaceLeft,
1524 .PERM => return error.PermissionDenied,
1525 .PIPE => return error.BrokenPipe,
1526 .NXIO => return error.Unseekable,
1527 .SPIPE => return error.Unseekable,
1528 .OVERFLOW => return error.Unseekable,
1529 .NOTCAPABLE => return error.AccessDenied,
1530 else => |err| return unexpectedErrno(err),
1531 }
1532 }
1533
1534 const pwritev_sym = if (lfs64_abi) system.pwritev64 else system.pwritev;
1535 while (true) {
1536 const rc = pwritev_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset));
1537 switch (errno(rc)) {
1538 .SUCCESS => return @intCast(rc),
1539 .INTR => continue,
1540 .INVAL => return error.InvalidArgument,
1541 .FAULT => unreachable,
1542 .SRCH => return error.ProcessNotFound,
1543 .AGAIN => return error.WouldBlock,
1544 .BADF => return error.NotOpenForWriting, // Can be a race condition.
1545 .DESTADDRREQ => unreachable, // `connect` was never called.
1546 .DQUOT => return error.DiskQuota,
1547 .FBIG => return error.FileTooBig,
1548 .IO => return error.InputOutput,
1549 .NOSPC => return error.NoSpaceLeft,
1550 .PERM => return error.PermissionDenied,
1551 .PIPE => return error.BrokenPipe,
1552 .NXIO => return error.Unseekable,
1553 .SPIPE => return error.Unseekable,
1554 .OVERFLOW => return error.Unseekable,
1555 .BUSY => return error.DeviceBusy,
1556 else => |err| return unexpectedErrno(err),
1557 }
1558 }
1559}
1560
1561pub const OpenError = std.Io.File.OpenError || error{WouldBlock};
1562
1563/// Open and possibly create a file. Keeps trying if it gets interrupted.
1564/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1565/// On WASI, `file_path` should be encoded as valid UTF-8.
1566/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1567/// See also `openZ`.
1568pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
1569 if (native_os == .windows) {
1570 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
1571 } else if (native_os == .wasi and !builtin.link_libc) {
1572 return openat(AT.FDCWD, file_path, flags, perm);
1573 }
1574 const file_path_c = try toPosixPath(file_path);
1575 return openZ(&file_path_c, flags, perm);
1576}
1577
1578/// Open and possibly create a file. Keeps trying if it gets interrupted.
1579/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1580/// On WASI, `file_path` should be encoded as valid UTF-8.
1581/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1582/// See also `open`.
1583pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
1584 if (native_os == .windows) {
1585 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
1586 } else if (native_os == .wasi and !builtin.link_libc) {
1587 return open(mem.sliceTo(file_path, 0), flags, perm);
1588 }
1589
1590 const open_sym = if (lfs64_abi) system.open64 else system.open;
1591 while (true) {
1592 const rc = open_sym(file_path, flags, perm);
1593 switch (errno(rc)) {
1594 .SUCCESS => return @intCast(rc),
1595 .INTR => continue,
1596
1597 .FAULT => unreachable,
1598 .INVAL => return error.BadPathName,
1599 .ACCES => return error.AccessDenied,
1600 .FBIG => return error.FileTooBig,
1601 .OVERFLOW => return error.FileTooBig,
1602 .ISDIR => return error.IsDir,
1603 .LOOP => return error.SymLinkLoop,
1604 .MFILE => return error.ProcessFdQuotaExceeded,
1605 .NAMETOOLONG => return error.NameTooLong,
1606 .NFILE => return error.SystemFdQuotaExceeded,
1607 .NODEV => return error.NoDevice,
1608 .NOENT => return error.FileNotFound,
1609 .SRCH => return error.ProcessNotFound,
1610 .NOMEM => return error.SystemResources,
1611 .NOSPC => return error.NoSpaceLeft,
1612 .NOTDIR => return error.NotDir,
1613 .PERM => return error.PermissionDenied,
1614 .EXIST => return error.PathAlreadyExists,
1615 .BUSY => return error.DeviceBusy,
1616 .ILSEQ => return error.BadPathName,
1617 else => |err| return unexpectedErrno(err),
1618 }
1619 }
1620}
1621
1622/// Open and possibly create a file. Keeps trying if it gets interrupted.
1623/// `file_path` is relative to the open directory handle `dir_fd`.
1624/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1625/// On WASI, `file_path` should be encoded as valid UTF-8.
1626/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1627/// See also `openatZ`.
1628pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {
1629 if (native_os == .windows) {
1630 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
1631 } else if (native_os == .wasi and !builtin.link_libc) {
1632 @compileError("use std.Io instead");
1633 }
1634 const file_path_c = try toPosixPath(file_path);
1635 return openatZ(dir_fd, &file_path_c, flags, mode);
1636}
1637
1638/// Open and possibly create a file. Keeps trying if it gets interrupted.
1639/// `file_path` is relative to the open directory handle `dir_fd`.
1640/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1641/// On WASI, `file_path` should be encoded as valid UTF-8.
1642/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1643/// See also `openat`.
1644pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {
1645 if (native_os == .windows) {
1646 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
1647 } else if (native_os == .wasi and !builtin.link_libc) {
1648 return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode);
1649 }
1650
1651 const openat_sym = if (lfs64_abi) system.openat64 else system.openat;
1652 while (true) {
1653 const rc = openat_sym(dir_fd, file_path, flags, mode);
1654 switch (errno(rc)) {
1655 .SUCCESS => return @intCast(rc),
1656 .INTR => continue,
1657
1658 .FAULT => unreachable,
1659 .INVAL => return error.BadPathName,
1660 .BADF => unreachable,
1661 .ACCES => return error.AccessDenied,
1662 .FBIG => return error.FileTooBig,
1663 .OVERFLOW => return error.FileTooBig,
1664 .ISDIR => return error.IsDir,
1665 .LOOP => return error.SymLinkLoop,
1666 .MFILE => return error.ProcessFdQuotaExceeded,
1667 .NAMETOOLONG => return error.NameTooLong,
1668 .NFILE => return error.SystemFdQuotaExceeded,
1669 .NODEV => return error.NoDevice,
1670 .NOENT => return error.FileNotFound,
1671 .SRCH => return error.ProcessNotFound,
1672 .NOMEM => return error.SystemResources,
1673 .NOSPC => return error.NoSpaceLeft,
1674 .NOTDIR => return error.NotDir,
1675 .PERM => return error.PermissionDenied,
1676 .EXIST => return error.PathAlreadyExists,
1677 .BUSY => return error.DeviceBusy,
1678 .OPNOTSUPP => return error.FileLocksNotSupported,
1679 .AGAIN => return error.WouldBlock,
1680 .TXTBSY => return error.FileBusy,
1681 .NXIO => return error.NoDevice,
1682 .ILSEQ => return error.BadPathName,
1683 else => |err| return unexpectedErrno(err),
1684 }
1685 }
1686}
1687
1688pub fn dup(old_fd: fd_t) !fd_t {
1689 const rc = system.dup(old_fd);
1690 return switch (errno(rc)) {
1691 .SUCCESS => return @intCast(rc),
1692 .MFILE => error.ProcessFdQuotaExceeded,
1693 .BADF => unreachable, // invalid file descriptor
1694 else => |err| return unexpectedErrno(err),
1695 };
1696}
1697
1698pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
1699 while (true) {
1700 switch (errno(system.dup2(old_fd, new_fd))) {
1701 .SUCCESS => return,
1702 .BUSY, .INTR => continue,
1703 .MFILE => return error.ProcessFdQuotaExceeded,
1704 .INVAL => unreachable, // invalid parameters passed to dup2
1705 .BADF => unreachable, // invalid file descriptor
1706 else => |err| return unexpectedErrno(err),
1707 }
1708 }
1709}
1710
1711pub fn getpid() pid_t {
1712 return system.getpid();
1713}
1714
1715pub fn getppid() pid_t {
1716 return system.getppid();
1717}
1718
1719pub const ExecveError = error{
1720 SystemResources,
1721 AccessDenied,
1722 PermissionDenied,
1723 InvalidExe,
1724 FileSystem,
1725 IsDir,
1726 FileNotFound,
1727 NotDir,
1728 FileBusy,
1729 ProcessFdQuotaExceeded,
1730 SystemFdQuotaExceeded,
1731 NameTooLong,
1732} || UnexpectedError;
1733
1734/// This function ignores PATH environment variable. See `execvpeZ` for that.
1735pub fn execveZ(
1736 path: [*:0]const u8,
1737 child_argv: [*:null]const ?[*:0]const u8,
1738 envp: [*:null]const ?[*:0]const u8,
1739) ExecveError {
1740 switch (errno(system.execve(path, child_argv, envp))) {
1741 .SUCCESS => unreachable,
1742 .FAULT => unreachable,
1743 .@"2BIG" => return error.SystemResources,
1744 .MFILE => return error.ProcessFdQuotaExceeded,
1745 .NAMETOOLONG => return error.NameTooLong,
1746 .NFILE => return error.SystemFdQuotaExceeded,
1747 .NOMEM => return error.SystemResources,
1748 .ACCES => return error.AccessDenied,
1749 .PERM => return error.PermissionDenied,
1750 .INVAL => return error.InvalidExe,
1751 .NOEXEC => return error.InvalidExe,
1752 .IO => return error.FileSystem,
1753 .LOOP => return error.FileSystem,
1754 .ISDIR => return error.IsDir,
1755 .NOENT => return error.FileNotFound,
1756 .NOTDIR => return error.NotDir,
1757 .TXTBSY => return error.FileBusy,
1758 else => |err| switch (native_os) {
1759 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) {
1760 .BADEXEC => return error.InvalidExe,
1761 .BADARCH => return error.InvalidExe,
1762 else => return unexpectedErrno(err),
1763 },
1764 .linux => switch (err) {
1765 .LIBBAD => return error.InvalidExe,
1766 else => return unexpectedErrno(err),
1767 },
1768 else => return unexpectedErrno(err),
1769 },
1770 }
1771}
1772
1773pub const Arg0Expand = enum {
1774 expand,
1775 no_expand,
1776};
1777
1778/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable,
1779/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall.
1780/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in.
1781pub fn execvpeZ_expandArg0(
1782 comptime arg0_expand: Arg0Expand,
1783 file: [*:0]const u8,
1784 child_argv: switch (arg0_expand) {
1785 .expand => [*:null]?[*:0]const u8,
1786 .no_expand => [*:null]const ?[*:0]const u8,
1787 },
1788 envp: [*:null]const ?[*:0]const u8,
1789) ExecveError {
1790 const file_slice = mem.sliceTo(file, 0);
1791 if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp);
1792
1793 const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
1794 // Use of PATH_MAX here is valid as the path_buf will be passed
1795 // directly to the operating system in execveZ.
1796 var path_buf: [PATH_MAX]u8 = undefined;
1797 var it = mem.tokenizeScalar(u8, PATH, ':');
1798 var seen_eacces = false;
1799 var err: ExecveError = error.FileNotFound;
1800
1801 // In case of expanding arg0 we must put it back if we return with an error.
1802 const prev_arg0 = child_argv[0];
1803 defer switch (arg0_expand) {
1804 .expand => child_argv[0] = prev_arg0,
1805 .no_expand => {},
1806 };
1807
1808 while (it.next()) |search_path| {
1809 const path_len = search_path.len + file_slice.len + 1;
1810 if (path_buf.len < path_len + 1) return error.NameTooLong;
1811 @memcpy(path_buf[0..search_path.len], search_path);
1812 path_buf[search_path.len] = '/';
1813 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
1814 path_buf[path_len] = 0;
1815 const full_path = path_buf[0..path_len :0].ptr;
1816 switch (arg0_expand) {
1817 .expand => child_argv[0] = full_path,
1818 .no_expand => {},
1819 }
1820 err = execveZ(full_path, child_argv, envp);
1821 switch (err) {
1822 error.AccessDenied => seen_eacces = true,
1823 error.FileNotFound, error.NotDir => {},
1824 else => |e| return e,
1825 }
1826 }
1827 if (seen_eacces) return error.AccessDenied;
1828 return err;
1829}
1830
1831/// This function also uses the PATH environment variable to get the full path to the executable.
1832/// If `file` is an absolute path, this is the same as `execveZ`.
1833pub fn execvpeZ(
1834 file: [*:0]const u8,
1835 argv_ptr: [*:null]const ?[*:0]const u8,
1836 envp: [*:null]const ?[*:0]const u8,
1837) ExecveError {
1838 return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp);
1839}
1840
1841/// Get an environment variable.
1842/// See also `getenvZ`.
1843pub fn getenv(key: []const u8) ?[:0]const u8 {
1844 if (native_os == .windows) {
1845 @compileError("std.posix.getenv is unavailable for Windows because environment strings are in WTF-16 format. See std.process.getEnvVarOwned for a cross-platform API or std.process.getenvW for a Windows-specific API.");
1846 }
1847 if (mem.findScalar(u8, key, '=') != null) {
1848 return null;
1849 }
1850 if (builtin.link_libc) {
1851 var ptr = std.c.environ;
1852 while (ptr[0]) |line| : (ptr += 1) {
1853 var line_i: usize = 0;
1854 while (line[line_i] != 0) : (line_i += 1) {
1855 if (line_i == key.len) break;
1856 if (line[line_i] != key[line_i]) break;
1857 }
1858 if ((line_i != key.len) or (line[line_i] != '=')) continue;
1859
1860 return mem.sliceTo(line + line_i + 1, 0);
1861 }
1862 return null;
1863 }
1864 if (native_os == .wasi) {
1865 @compileError("std.posix.getenv is unavailable for WASI. See std.process.getEnvMap or std.process.getEnvVarOwned for a cross-platform API.");
1866 }
1867 // The simplified start logic doesn't populate environ.
1868 if (std.start.simplified_logic) return null;
1869 // TODO see https://github.com/ziglang/zig/issues/4524
1870 for (std.os.environ) |ptr| {
1871 var line_i: usize = 0;
1872 while (ptr[line_i] != 0) : (line_i += 1) {
1873 if (line_i == key.len) break;
1874 if (ptr[line_i] != key[line_i]) break;
1875 }
1876 if ((line_i != key.len) or (ptr[line_i] != '=')) continue;
1877
1878 return mem.sliceTo(ptr + line_i + 1, 0);
1879 }
1880 return null;
1881}
1882
1883/// Get an environment variable with a null-terminated name.
1884/// See also `getenv`.
1885pub fn getenvZ(key: [*:0]const u8) ?[:0]const u8 {
1886 if (builtin.link_libc) {
1887 const value = system.getenv(key) orelse return null;
1888 return mem.sliceTo(value, 0);
1889 }
1890 if (native_os == .windows) {
1891 @compileError("std.posix.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.process.getenvW for Windows-specific API.");
1892 }
1893 return getenv(mem.sliceTo(key, 0));
1894}
1895
1896pub const GetCwdError = error{
1897 NameTooLong,
1898 CurrentWorkingDirectoryUnlinked,
1899} || UnexpectedError;
1900
1901/// The result is a slice of out_buffer, indexed from 0.
1902pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
1903 if (native_os == .windows) {
1904 return windows.GetCurrentDirectory(out_buffer);
1905 } else if (native_os == .wasi and !builtin.link_libc) {
1906 const path = ".";
1907 if (out_buffer.len < path.len) return error.NameTooLong;
1908 const result = out_buffer[0..path.len];
1909 @memcpy(result, path);
1910 return result;
1911 }
1912
1913 const err: E = if (builtin.link_libc) err: {
1914 const c_err = if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*;
1915 break :err @enumFromInt(c_err);
1916 } else err: {
1917 break :err errno(system.getcwd(out_buffer.ptr, out_buffer.len));
1918 };
1919 switch (err) {
1920 .SUCCESS => return mem.sliceTo(out_buffer, 0),
1921 .FAULT => unreachable,
1922 .INVAL => unreachable,
1923 .NOENT => return error.CurrentWorkingDirectoryUnlinked,
1924 .RANGE => return error.NameTooLong,
1925 else => return unexpectedErrno(err),
1926 }
1927}
1928
1929pub const SymLinkError = error{
1930 /// In WASI, this error may occur when the file descriptor does
1931 /// not hold the required rights to create a new symbolic link relative to it.
1932 AccessDenied,
1933 PermissionDenied,
1934 DiskQuota,
1935 PathAlreadyExists,
1936 FileSystem,
1937 SymLinkLoop,
1938 FileNotFound,
1939 SystemResources,
1940 NoSpaceLeft,
1941 ReadOnlyFileSystem,
1942 NotDir,
1943 NameTooLong,
1944 /// WASI: file paths must be valid UTF-8.
1945 /// Windows: file paths provided by the user must be valid WTF-8.
1946 /// https://wtf-8.codeberg.page/
1947 BadPathName,
1948} || UnexpectedError;
1949
1950/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1951/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1952/// one; the latter case is known as a dangling link.
1953/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1954/// On WASI, both paths should be encoded as valid UTF-8.
1955/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1956/// If `sym_link_path` exists, it will not be overwritten.
1957/// See also `symlinkZ.
1958pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
1959 if (native_os == .windows) {
1960 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
1961 } else if (native_os == .wasi and !builtin.link_libc) {
1962 return symlinkat(target_path, AT.FDCWD, sym_link_path);
1963 }
1964 const target_path_c = try toPosixPath(target_path);
1965 const sym_link_path_c = try toPosixPath(sym_link_path);
1966 return symlinkZ(&target_path_c, &sym_link_path_c);
1967}
1968
1969/// This is the same as `symlink` except the parameters are null-terminated pointers.
1970/// See also `symlink`.
1971pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
1972 if (native_os == .windows) {
1973 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
1974 } else if (native_os == .wasi and !builtin.link_libc) {
1975 return symlinkatZ(target_path, fs.cwd().fd, sym_link_path);
1976 }
1977 switch (errno(system.symlink(target_path, sym_link_path))) {
1978 .SUCCESS => return,
1979 .FAULT => unreachable,
1980 .INVAL => unreachable,
1981 .ACCES => return error.AccessDenied,
1982 .PERM => return error.PermissionDenied,
1983 .DQUOT => return error.DiskQuota,
1984 .EXIST => return error.PathAlreadyExists,
1985 .IO => return error.FileSystem,
1986 .LOOP => return error.SymLinkLoop,
1987 .NAMETOOLONG => return error.NameTooLong,
1988 .NOENT => return error.FileNotFound,
1989 .NOTDIR => return error.NotDir,
1990 .NOMEM => return error.SystemResources,
1991 .NOSPC => return error.NoSpaceLeft,
1992 .ROFS => return error.ReadOnlyFileSystem,
1993 .ILSEQ => return error.BadPathName,
1994 else => |err| return unexpectedErrno(err),
1995 }
1996}
1997
1998/// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string
1999/// `target_path` **relative** to `newdirfd` directory handle.
2000/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
2001/// one; the latter case is known as a dangling link.
2002/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2003/// On WASI, both paths should be encoded as valid UTF-8.
2004/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2005/// If `sym_link_path` exists, it will not be overwritten.
2006/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
2007pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
2008 if (native_os == .windows) {
2009 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2010 } else if (native_os == .wasi and !builtin.link_libc) {
2011 return symlinkatWasi(target_path, newdirfd, sym_link_path);
2012 }
2013 const target_path_c = try toPosixPath(target_path);
2014 const sym_link_path_c = try toPosixPath(sym_link_path);
2015 return symlinkatZ(&target_path_c, newdirfd, &sym_link_path_c);
2016}
2017
2018/// WASI-only. The same as `symlinkat` but targeting WASI.
2019/// See also `symlinkat`.
2020pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
2021 switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) {
2022 .SUCCESS => {},
2023 .FAULT => unreachable,
2024 .INVAL => unreachable,
2025 .BADF => unreachable,
2026 .ACCES => return error.AccessDenied,
2027 .PERM => return error.PermissionDenied,
2028 .DQUOT => return error.DiskQuota,
2029 .EXIST => return error.PathAlreadyExists,
2030 .IO => return error.FileSystem,
2031 .LOOP => return error.SymLinkLoop,
2032 .NAMETOOLONG => return error.NameTooLong,
2033 .NOENT => return error.FileNotFound,
2034 .NOTDIR => return error.NotDir,
2035 .NOMEM => return error.SystemResources,
2036 .NOSPC => return error.NoSpaceLeft,
2037 .ROFS => return error.ReadOnlyFileSystem,
2038 .NOTCAPABLE => return error.AccessDenied,
2039 .ILSEQ => return error.BadPathName,
2040 else => |err| return unexpectedErrno(err),
2041 }
2042}
2043
2044/// The same as `symlinkat` except the parameters are null-terminated pointers.
2045/// See also `symlinkat`.
2046pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
2047 if (native_os == .windows) {
2048 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2049 } else if (native_os == .wasi and !builtin.link_libc) {
2050 return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0));
2051 }
2052 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
2053 .SUCCESS => return,
2054 .FAULT => unreachable,
2055 .INVAL => unreachable,
2056 .ACCES => return error.AccessDenied,
2057 .PERM => return error.PermissionDenied,
2058 .DQUOT => return error.DiskQuota,
2059 .EXIST => return error.PathAlreadyExists,
2060 .IO => return error.FileSystem,
2061 .LOOP => return error.SymLinkLoop,
2062 .NAMETOOLONG => return error.NameTooLong,
2063 .NOENT => return error.FileNotFound,
2064 .NOTDIR => return error.NotDir,
2065 .NOMEM => return error.SystemResources,
2066 .NOSPC => return error.NoSpaceLeft,
2067 .ROFS => return error.ReadOnlyFileSystem,
2068 .ILSEQ => return error.BadPathName,
2069 else => |err| return unexpectedErrno(err),
2070 }
2071}
2072
2073pub const LinkError = UnexpectedError || error{
2074 AccessDenied,
2075 PermissionDenied,
2076 DiskQuota,
2077 PathAlreadyExists,
2078 FileSystem,
2079 SymLinkLoop,
2080 LinkQuotaExceeded,
2081 NameTooLong,
2082 FileNotFound,
2083 SystemResources,
2084 NoSpaceLeft,
2085 ReadOnlyFileSystem,
2086 NotSameFileSystem,
2087 BadPathName,
2088};
2089
2090/// On WASI, both paths should be encoded as valid UTF-8.
2091/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2092pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8) LinkError!void {
2093 if (native_os == .wasi and !builtin.link_libc) {
2094 return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0));
2095 }
2096 switch (errno(system.link(oldpath, newpath))) {
2097 .SUCCESS => return,
2098 .ACCES => return error.AccessDenied,
2099 .DQUOT => return error.DiskQuota,
2100 .EXIST => return error.PathAlreadyExists,
2101 .FAULT => unreachable,
2102 .IO => return error.FileSystem,
2103 .LOOP => return error.SymLinkLoop,
2104 .MLINK => return error.LinkQuotaExceeded,
2105 .NAMETOOLONG => return error.NameTooLong,
2106 .NOENT => return error.FileNotFound,
2107 .NOMEM => return error.SystemResources,
2108 .NOSPC => return error.NoSpaceLeft,
2109 .PERM => return error.PermissionDenied,
2110 .ROFS => return error.ReadOnlyFileSystem,
2111 .XDEV => return error.NotSameFileSystem,
2112 .INVAL => unreachable,
2113 .ILSEQ => return error.BadPathName,
2114 else => |err| return unexpectedErrno(err),
2115 }
2116}
2117
2118/// On WASI, both paths should be encoded as valid UTF-8.
2119/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2120pub fn link(oldpath: []const u8, newpath: []const u8) LinkError!void {
2121 if (native_os == .wasi and !builtin.link_libc) {
2122 return linkat(AT.FDCWD, oldpath, AT.FDCWD, newpath, 0) catch |err| switch (err) {
2123 error.NotDir => unreachable, // link() does not support directories
2124 else => |e| return e,
2125 };
2126 }
2127 const old = try toPosixPath(oldpath);
2128 const new = try toPosixPath(newpath);
2129 return try linkZ(&old, &new);
2130}
2131
2132pub const LinkatError = LinkError || error{NotDir};
2133
2134/// On WASI, both paths should be encoded as valid UTF-8.
2135/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2136pub fn linkatZ(
2137 olddir: fd_t,
2138 oldpath: [*:0]const u8,
2139 newdir: fd_t,
2140 newpath: [*:0]const u8,
2141 flags: i32,
2142) LinkatError!void {
2143 if (native_os == .wasi and !builtin.link_libc) {
2144 return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags);
2145 }
2146 switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) {
2147 .SUCCESS => return,
2148 .ACCES => return error.AccessDenied,
2149 .DQUOT => return error.DiskQuota,
2150 .EXIST => return error.PathAlreadyExists,
2151 .FAULT => unreachable,
2152 .IO => return error.FileSystem,
2153 .LOOP => return error.SymLinkLoop,
2154 .MLINK => return error.LinkQuotaExceeded,
2155 .NAMETOOLONG => return error.NameTooLong,
2156 .NOENT => return error.FileNotFound,
2157 .NOMEM => return error.SystemResources,
2158 .NOSPC => return error.NoSpaceLeft,
2159 .NOTDIR => return error.NotDir,
2160 .PERM => return error.PermissionDenied,
2161 .ROFS => return error.ReadOnlyFileSystem,
2162 .XDEV => return error.NotSameFileSystem,
2163 .INVAL => unreachable,
2164 .ILSEQ => return error.BadPathName,
2165 else => |err| return unexpectedErrno(err),
2166 }
2167}
2168
2169/// On WASI, both paths should be encoded as valid UTF-8.
2170/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2171pub fn linkat(
2172 olddir: fd_t,
2173 oldpath: []const u8,
2174 newdir: fd_t,
2175 newpath: []const u8,
2176 flags: i32,
2177) LinkatError!void {
2178 if (native_os == .wasi and !builtin.link_libc) {
2179 const old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath };
2180 const new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath };
2181 const old_flags: wasi.lookupflags_t = .{
2182 .SYMLINK_FOLLOW = (flags & AT.SYMLINK_FOLLOW) != 0,
2183 };
2184 switch (wasi.path_link(
2185 old.dir_fd,
2186 old_flags,
2187 old.relative_path.ptr,
2188 old.relative_path.len,
2189 new.dir_fd,
2190 new.relative_path.ptr,
2191 new.relative_path.len,
2192 )) {
2193 .SUCCESS => return,
2194 .ACCES => return error.AccessDenied,
2195 .DQUOT => return error.DiskQuota,
2196 .EXIST => return error.PathAlreadyExists,
2197 .FAULT => unreachable,
2198 .IO => return error.FileSystem,
2199 .LOOP => return error.SymLinkLoop,
2200 .MLINK => return error.LinkQuotaExceeded,
2201 .NAMETOOLONG => return error.NameTooLong,
2202 .NOENT => return error.FileNotFound,
2203 .NOMEM => return error.SystemResources,
2204 .NOSPC => return error.NoSpaceLeft,
2205 .NOTDIR => return error.NotDir,
2206 .PERM => return error.PermissionDenied,
2207 .ROFS => return error.ReadOnlyFileSystem,
2208 .XDEV => return error.NotSameFileSystem,
2209 .INVAL => unreachable,
2210 .ILSEQ => return error.BadPathName,
2211 else => |err| return unexpectedErrno(err),
2212 }
2213 }
2214 const old = try toPosixPath(oldpath);
2215 const new = try toPosixPath(newpath);
2216 return try linkatZ(olddir, &old, newdir, &new, flags);
2217}
2218
2219pub const UnlinkError = error{
2220 FileNotFound,
2221
2222 /// In WASI, this error may occur when the file descriptor does
2223 /// not hold the required rights to unlink a resource by path relative to it.
2224 AccessDenied,
2225 PermissionDenied,
2226 FileBusy,
2227 FileSystem,
2228 IsDir,
2229 SymLinkLoop,
2230 NameTooLong,
2231 NotDir,
2232 SystemResources,
2233 ReadOnlyFileSystem,
2234
2235 /// WASI: file paths must be valid UTF-8.
2236 /// Windows: file paths provided by the user must be valid WTF-8.
2237 /// https://wtf-8.codeberg.page/
2238 /// Windows: file paths cannot contain these characters:
2239 /// '/', '*', '?', '"', '<', '>', '|'
2240 BadPathName,
2241
2242 /// On Windows, `\\server` or `\\server\share` was not found.
2243 NetworkNotFound,
2244} || UnexpectedError;
2245
2246/// Delete a name and possibly the file it refers to.
2247/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2248/// On WASI, `file_path` should be encoded as valid UTF-8.
2249/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
2250/// See also `unlinkZ`.
2251pub fn unlink(file_path: []const u8) UnlinkError!void {
2252 if (native_os == .wasi and !builtin.link_libc) {
2253 return unlinkat(AT.FDCWD, file_path, 0) catch |err| switch (err) {
2254 error.DirNotEmpty => unreachable, // only occurs when targeting directories
2255 else => |e| return e,
2256 };
2257 } else if (native_os == .windows) {
2258 const file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
2259 return unlinkW(file_path_w.span());
2260 } else {
2261 const file_path_c = try toPosixPath(file_path);
2262 return unlinkZ(&file_path_c);
2263 }
2264}
2265
2266/// Same as `unlink` except the parameter is null terminated.
2267pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
2268 if (native_os == .windows) {
2269 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
2270 return unlinkW(file_path_w.span());
2271 } else if (native_os == .wasi and !builtin.link_libc) {
2272 return unlink(mem.sliceTo(file_path, 0));
2273 }
2274 switch (errno(system.unlink(file_path))) {
2275 .SUCCESS => return,
2276 .ACCES => return error.AccessDenied,
2277 .PERM => return error.PermissionDenied,
2278 .BUSY => return error.FileBusy,
2279 .FAULT => unreachable,
2280 .INVAL => unreachable,
2281 .IO => return error.FileSystem,
2282 .ISDIR => return error.IsDir,
2283 .LOOP => return error.SymLinkLoop,
2284 .NAMETOOLONG => return error.NameTooLong,
2285 .NOENT => return error.FileNotFound,
2286 .NOTDIR => return error.NotDir,
2287 .NOMEM => return error.SystemResources,
2288 .ROFS => return error.ReadOnlyFileSystem,
2289 .ILSEQ => return error.BadPathName,
2290 else => |err| return unexpectedErrno(err),
2291 }
2292}
2293
2294/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded.
2295pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
2296 windows.DeleteFile(file_path_w, .{ .dir = fs.cwd().fd }) catch |err| switch (err) {
2297 error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true
2298 else => |e| return e,
2299 };
768pub fn getpid() pid_t {
769 return system.getpid();
2300770}
2301771
2302pub const UnlinkatError = UnlinkError || error{
2303 /// When passing `AT.REMOVEDIR`, this error occurs when the named directory is not empty.
2304 DirNotEmpty,
2305};
2306
2307/// Delete a file name and possibly the file it refers to, based on an open directory handle.
2308/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2309/// On WASI, `file_path` should be encoded as valid UTF-8.
2310/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
2311/// Asserts that the path parameter has no null bytes.
2312pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
2313 if (native_os == .windows) {
2314 const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
2315 return unlinkatW(dirfd, file_path_w.span(), flags);
2316 } else if (native_os == .wasi and !builtin.link_libc) {
2317 return unlinkatWasi(dirfd, file_path, flags);
2318 } else {
2319 const file_path_c = try toPosixPath(file_path);
2320 return unlinkatZ(dirfd, &file_path_c, flags);
2321 }
772pub fn getppid() pid_t {
773 return system.getppid();
2322774}
2323775
2324/// WASI-only. Same as `unlinkat` but targeting WASI.
2325/// See also `unlinkat`.
2326pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
2327 const remove_dir = (flags & AT.REMOVEDIR) != 0;
2328 const res = if (remove_dir)
2329 wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len)
2330 else
2331 wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len);
2332 switch (res) {
2333 .SUCCESS => return,
2334 .ACCES => return error.AccessDenied,
2335 .PERM => return error.PermissionDenied,
2336 .BUSY => return error.FileBusy,
776pub const ExecveError = error{
777 SystemResources,
778 AccessDenied,
779 PermissionDenied,
780 InvalidExe,
781 FileSystem,
782 IsDir,
783 FileNotFound,
784 NotDir,
785 FileBusy,
786 ProcessFdQuotaExceeded,
787 SystemFdQuotaExceeded,
788 NameTooLong,
789} || UnexpectedError;
790
791/// This function ignores PATH environment variable. See `execvpeZ` for that.
792pub fn execveZ(
793 path: [*:0]const u8,
794 child_argv: [*:null]const ?[*:0]const u8,
795 envp: [*:null]const ?[*:0]const u8,
796) ExecveError {
797 switch (errno(system.execve(path, child_argv, envp))) {
798 .SUCCESS => unreachable,
2337799 .FAULT => unreachable,
2338 .IO => return error.FileSystem,
2339 .ISDIR => return error.IsDir,
2340 .LOOP => return error.SymLinkLoop,
800 .@"2BIG" => return error.SystemResources,
801 .MFILE => return error.ProcessFdQuotaExceeded,
2341802 .NAMETOOLONG => return error.NameTooLong,
2342 .NOENT => return error.FileNotFound,
2343 .NOTDIR => return error.NotDir,
803 .NFILE => return error.SystemFdQuotaExceeded,
2344804 .NOMEM => return error.SystemResources,
2345 .ROFS => return error.ReadOnlyFileSystem,
2346 .NOTEMPTY => return error.DirNotEmpty,
2347 .NOTCAPABLE => return error.AccessDenied,
2348 .ILSEQ => return error.BadPathName,
2349
2350 .INVAL => unreachable, // invalid flags, or pathname has . as last component
2351 .BADF => unreachable, // always a race condition
2352
2353 else => |err| return unexpectedErrno(err),
2354 }
2355}
2356
2357/// Same as `unlinkat` but `file_path` is a null-terminated string.
2358pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
2359 if (native_os == .windows) {
2360 const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path_c);
2361 return unlinkatW(dirfd, file_path_w.span(), flags);
2362 } else if (native_os == .wasi and !builtin.link_libc) {
2363 return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags);
2364 }
2365 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
2366 .SUCCESS => return,
2367805 .ACCES => return error.AccessDenied,
2368806 .PERM => return error.PermissionDenied,
2369 .BUSY => return error.FileBusy,
2370 .FAULT => unreachable,
807 .INVAL => return error.InvalidExe,
808 .NOEXEC => return error.InvalidExe,
2371809 .IO => return error.FileSystem,
810 .LOOP => return error.FileSystem,
2372811 .ISDIR => return error.IsDir,
2373 .LOOP => return error.SymLinkLoop,
2374 .NAMETOOLONG => return error.NameTooLong,
2375812 .NOENT => return error.FileNotFound,
2376813 .NOTDIR => return error.NotDir,
2377 .NOMEM => return error.SystemResources,
2378 .ROFS => return error.ReadOnlyFileSystem,
2379 .EXIST => return error.DirNotEmpty,
2380 .NOTEMPTY => return error.DirNotEmpty,
2381 .ILSEQ => return error.BadPathName,
2382
2383 .INVAL => unreachable, // invalid flags, or pathname has . as last component
2384 .BADF => unreachable, // always a race condition
2385
2386 else => |err| return unexpectedErrno(err),
814 .TXTBSY => return error.FileBusy,
815 else => |err| switch (native_os) {
816 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) {
817 .BADEXEC => return error.InvalidExe,
818 .BADARCH => return error.InvalidExe,
819 else => return unexpectedErrno(err),
820 },
821 .linux => switch (err) {
822 .LIBBAD => return error.InvalidExe,
823 else => return unexpectedErrno(err),
824 },
825 else => return unexpectedErrno(err),
826 },
2387827 }
2388828}
2389829
2390/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only.
2391pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
2392 const remove_dir = (flags & AT.REMOVEDIR) != 0;
2393 return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
2394}
830pub const Arg0Expand = enum {
831 expand,
832 no_expand,
833};
2395834
2396pub const RenameError = error{
2397 /// In WASI, this error may occur when the file descriptor does
2398 /// not hold the required rights to rename a resource by path relative to it.
2399 ///
2400 /// On Windows, this error may be returned instead of PathAlreadyExists when
2401 /// renaming a directory over an existing directory.
2402 AccessDenied,
2403 PermissionDenied,
2404 FileBusy,
2405 DiskQuota,
2406 IsDir,
2407 SymLinkLoop,
2408 LinkQuotaExceeded,
2409 NameTooLong,
2410 FileNotFound,
2411 NotDir,
2412 SystemResources,
2413 NoSpaceLeft,
2414 PathAlreadyExists,
2415 ReadOnlyFileSystem,
2416 RenameAcrossMountPoints,
2417 /// WASI: file paths must be valid UTF-8.
2418 /// Windows: file paths provided by the user must be valid WTF-8.
2419 /// https://wtf-8.codeberg.page/
2420 BadPathName,
2421 NoDevice,
2422 SharingViolation,
2423 PipeBusy,
2424 /// On Windows, `\\server` or `\\server\share` was not found.
2425 NetworkNotFound,
2426 /// On Windows, antivirus software is enabled by default. It can be
2427 /// disabled, but Windows Update sometimes ignores the user's preference
2428 /// and re-enables it. When enabled, antivirus software on Windows
2429 /// intercepts file system operations and makes them significantly slower
2430 /// in addition to possibly failing with this error code.
2431 AntivirusInterference,
2432} || UnexpectedError;
835/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable,
836/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall.
837/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in.
838pub fn execvpeZ_expandArg0(
839 comptime arg0_expand: Arg0Expand,
840 file: [*:0]const u8,
841 child_argv: switch (arg0_expand) {
842 .expand => [*:null]?[*:0]const u8,
843 .no_expand => [*:null]const ?[*:0]const u8,
844 },
845 envp: [*:null]const ?[*:0]const u8,
846) ExecveError {
847 const file_slice = mem.sliceTo(file, 0);
848 if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp);
2433849
2434/// Change the name or location of a file.
2435/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2436/// On WASI, both paths should be encoded as valid UTF-8.
2437/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2438pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
2439 if (native_os == .wasi and !builtin.link_libc) {
2440 return renameat(AT.FDCWD, old_path, AT.FDCWD, new_path);
2441 } else if (native_os == .windows) {
2442 const old_path_w = try windows.sliceToPrefixedFileW(null, old_path);
2443 const new_path_w = try windows.sliceToPrefixedFileW(null, new_path);
2444 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
2445 } else {
2446 const old_path_c = try toPosixPath(old_path);
2447 const new_path_c = try toPosixPath(new_path);
2448 return renameZ(&old_path_c, &new_path_c);
850 const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
851 // Use of PATH_MAX here is valid as the path_buf will be passed
852 // directly to the operating system in execveZ.
853 var path_buf: [PATH_MAX]u8 = undefined;
854 var it = mem.tokenizeScalar(u8, PATH, ':');
855 var seen_eacces = false;
856 var err: ExecveError = error.FileNotFound;
857
858 // In case of expanding arg0 we must put it back if we return with an error.
859 const prev_arg0 = child_argv[0];
860 defer switch (arg0_expand) {
861 .expand => child_argv[0] = prev_arg0,
862 .no_expand => {},
863 };
864
865 while (it.next()) |search_path| {
866 const path_len = search_path.len + file_slice.len + 1;
867 if (path_buf.len < path_len + 1) return error.NameTooLong;
868 @memcpy(path_buf[0..search_path.len], search_path);
869 path_buf[search_path.len] = '/';
870 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
871 path_buf[path_len] = 0;
872 const full_path = path_buf[0..path_len :0].ptr;
873 switch (arg0_expand) {
874 .expand => child_argv[0] = full_path,
875 .no_expand => {},
876 }
877 err = execveZ(full_path, child_argv, envp);
878 switch (err) {
879 error.AccessDenied => seen_eacces = true,
880 error.FileNotFound, error.NotDir => {},
881 else => |e| return e,
882 }
2449883 }
884 if (seen_eacces) return error.AccessDenied;
885 return err;
886}
887
888/// This function also uses the PATH environment variable to get the full path to the executable.
889/// If `file` is an absolute path, this is the same as `execveZ`.
890pub fn execvpeZ(
891 file: [*:0]const u8,
892 argv_ptr: [*:null]const ?[*:0]const u8,
893 envp: [*:null]const ?[*:0]const u8,
894) ExecveError {
895 return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp);
2450896}
2451897
2452/// Same as `rename` except the parameters are null-terminated.
2453pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
898/// Get an environment variable.
899/// See also `getenvZ`.
900pub fn getenv(key: []const u8) ?[:0]const u8 {
2454901 if (native_os == .windows) {
2455 const old_path_w = try windows.cStrToPrefixedFileW(null, old_path);
2456 const new_path_w = try windows.cStrToPrefixedFileW(null, new_path);
2457 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
2458 } else if (native_os == .wasi and !builtin.link_libc) {
2459 return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0));
902 @compileError("std.posix.getenv is unavailable for Windows because environment strings are in WTF-16 format. See std.process.getEnvVarOwned for a cross-platform API or std.process.getenvW for a Windows-specific API.");
2460903 }
2461 switch (errno(system.rename(old_path, new_path))) {
2462 .SUCCESS => return,
2463 .ACCES => return error.AccessDenied,
2464 .PERM => return error.PermissionDenied,
2465 .BUSY => return error.FileBusy,
2466 .DQUOT => return error.DiskQuota,
2467 .FAULT => unreachable,
2468 .INVAL => unreachable,
2469 .ISDIR => return error.IsDir,
2470 .LOOP => return error.SymLinkLoop,
2471 .MLINK => return error.LinkQuotaExceeded,
2472 .NAMETOOLONG => return error.NameTooLong,
2473 .NOENT => return error.FileNotFound,
2474 .NOTDIR => return error.NotDir,
2475 .NOMEM => return error.SystemResources,
2476 .NOSPC => return error.NoSpaceLeft,
2477 .EXIST => return error.PathAlreadyExists,
2478 .NOTEMPTY => return error.PathAlreadyExists,
2479 .ROFS => return error.ReadOnlyFileSystem,
2480 .XDEV => return error.RenameAcrossMountPoints,
2481 .ILSEQ => return error.BadPathName,
2482 else => |err| return unexpectedErrno(err),
904 if (mem.findScalar(u8, key, '=') != null) {
905 return null;
2483906 }
2484}
907 if (builtin.link_libc) {
908 var ptr = std.c.environ;
909 while (ptr[0]) |line| : (ptr += 1) {
910 var line_i: usize = 0;
911 while (line[line_i] != 0) : (line_i += 1) {
912 if (line_i == key.len) break;
913 if (line[line_i] != key[line_i]) break;
914 }
915 if ((line_i != key.len) or (line[line_i] != '=')) continue;
2485916
2486/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded.
2487/// Assumes target is Windows.
2488pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
2489 const cwd_handle = std.fs.cwd().fd;
2490 return windows.RenameFile(cwd_handle, mem.span(old_path), cwd_handle, mem.span(new_path), true);
2491}
917 return mem.sliceTo(line + line_i + 1, 0);
918 }
919 return null;
920 }
921 if (native_os == .wasi) {
922 @compileError("std.posix.getenv is unavailable for WASI. See std.process.getEnvMap or std.process.getEnvVarOwned for a cross-platform API.");
923 }
924 // The simplified start logic doesn't populate environ.
925 if (std.start.simplified_logic) return null;
926 // TODO see https://github.com/ziglang/zig/issues/4524
927 for (std.os.environ) |ptr| {
928 var line_i: usize = 0;
929 while (ptr[line_i] != 0) : (line_i += 1) {
930 if (line_i == key.len) break;
931 if (ptr[line_i] != key[line_i]) break;
932 }
933 if ((line_i != key.len) or (ptr[line_i] != '=')) continue;
2492934
2493/// Change the name or location of a file based on an open directory handle.
2494/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2495/// On WASI, both paths should be encoded as valid UTF-8.
2496/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2497pub fn renameat(
2498 old_dir_fd: fd_t,
2499 old_path: []const u8,
2500 new_dir_fd: fd_t,
2501 new_path: []const u8,
2502) RenameError!void {
2503 if (native_os == .windows) {
2504 const old_path_w = try windows.sliceToPrefixedFileW(old_dir_fd, old_path);
2505 const new_path_w = try windows.sliceToPrefixedFileW(new_dir_fd, new_path);
2506 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
2507 } else if (native_os == .wasi and !builtin.link_libc) {
2508 const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path };
2509 const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path };
2510 return renameatWasi(old, new);
2511 } else {
2512 const old_path_c = try toPosixPath(old_path);
2513 const new_path_c = try toPosixPath(new_path);
2514 return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c);
935 return mem.sliceTo(ptr + line_i + 1, 0);
2515936 }
937 return null;
2516938}
2517939
2518/// WASI-only. Same as `renameat` expect targeting WASI.
2519/// See also `renameat`.
2520fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {
2521 switch (wasi.path_rename(old.dir_fd, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) {
2522 .SUCCESS => return,
2523 .ACCES => return error.AccessDenied,
2524 .PERM => return error.PermissionDenied,
2525 .BUSY => return error.FileBusy,
2526 .DQUOT => return error.DiskQuota,
2527 .FAULT => unreachable,
2528 .INVAL => unreachable,
2529 .ISDIR => return error.IsDir,
2530 .LOOP => return error.SymLinkLoop,
2531 .MLINK => return error.LinkQuotaExceeded,
2532 .NAMETOOLONG => return error.NameTooLong,
2533 .NOENT => return error.FileNotFound,
2534 .NOTDIR => return error.NotDir,
2535 .NOMEM => return error.SystemResources,
2536 .NOSPC => return error.NoSpaceLeft,
2537 .EXIST => return error.PathAlreadyExists,
2538 .NOTEMPTY => return error.PathAlreadyExists,
2539 .ROFS => return error.ReadOnlyFileSystem,
2540 .XDEV => return error.RenameAcrossMountPoints,
2541 .NOTCAPABLE => return error.AccessDenied,
2542 .ILSEQ => return error.BadPathName,
2543 else => |err| return unexpectedErrno(err),
940/// Get an environment variable with a null-terminated name.
941/// See also `getenv`.
942pub fn getenvZ(key: [*:0]const u8) ?[:0]const u8 {
943 if (builtin.link_libc) {
944 const value = system.getenv(key) orelse return null;
945 return mem.sliceTo(value, 0);
946 }
947 if (native_os == .windows) {
948 @compileError("std.posix.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.process.getenvW for Windows-specific API.");
2544949 }
950 return getenv(mem.sliceTo(key, 0));
2545951}
2546952
2547/// An fd-relative file path
2548///
2549/// This is currently only used for WASI-specific functionality, but the concept
2550/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions.
2551const RelativePathWasi = struct {
2552 /// Handle to directory
2553 dir_fd: fd_t,
2554 /// Path to resource within `dir_fd`.
2555 relative_path: []const u8,
2556};
953pub const GetCwdError = error{
954 NameTooLong,
955 CurrentWorkingDirectoryUnlinked,
956} || UnexpectedError;
2557957
2558/// Same as `renameat` except the parameters are null-terminated.
2559pub fn renameatZ(
2560 old_dir_fd: fd_t,
2561 old_path: [*:0]const u8,
2562 new_dir_fd: fd_t,
2563 new_path: [*:0]const u8,
2564) RenameError!void {
958/// The result is a slice of out_buffer, indexed from 0.
959pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
2565960 if (native_os == .windows) {
2566 const old_path_w = try windows.cStrToPrefixedFileW(old_dir_fd, old_path);
2567 const new_path_w = try windows.cStrToPrefixedFileW(new_dir_fd, new_path);
2568 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
961 return windows.GetCurrentDirectory(out_buffer);
2569962 } else if (native_os == .wasi and !builtin.link_libc) {
2570 return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0));
963 const path = ".";
964 if (out_buffer.len < path.len) return error.NameTooLong;
965 const result = out_buffer[0..path.len];
966 @memcpy(result, path);
967 return result;
2571968 }
2572969
2573 switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
2574 .SUCCESS => return,
2575 .ACCES => return error.AccessDenied,
2576 .PERM => return error.PermissionDenied,
2577 .BUSY => return error.FileBusy,
2578 .DQUOT => return error.DiskQuota,
970 const err: E = if (builtin.link_libc) err: {
971 const c_err = if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*;
972 break :err @enumFromInt(c_err);
973 } else err: {
974 break :err errno(system.getcwd(out_buffer.ptr, out_buffer.len));
975 };
976 switch (err) {
977 .SUCCESS => return mem.sliceTo(out_buffer, 0),
2579978 .FAULT => unreachable,
2580979 .INVAL => unreachable,
2581 .ISDIR => return error.IsDir,
2582 .LOOP => return error.SymLinkLoop,
2583 .MLINK => return error.LinkQuotaExceeded,
2584 .NAMETOOLONG => return error.NameTooLong,
2585 .NOENT => return error.FileNotFound,
2586 .NOTDIR => return error.NotDir,
2587 .NOMEM => return error.SystemResources,
2588 .NOSPC => return error.NoSpaceLeft,
2589 .EXIST => return error.PathAlreadyExists,
2590 .NOTEMPTY => return error.PathAlreadyExists,
2591 .ROFS => return error.ReadOnlyFileSystem,
2592 .XDEV => return error.RenameAcrossMountPoints,
2593 .ILSEQ => return error.BadPathName,
2594 else => |err| return unexpectedErrno(err),
980 .NOENT => return error.CurrentWorkingDirectoryUnlinked,
981 .RANGE => return error.NameTooLong,
982 else => return unexpectedErrno(err),
2595983 }
2596984}
2597985
2598/// Same as `renameat` but Windows-only and the path parameters are
2599/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded.
2600pub fn renameatW(
2601 old_dir_fd: fd_t,
2602 old_path_w: []const u16,
2603 new_dir_fd: fd_t,
2604 new_path_w: []const u16,
2605 ReplaceIfExists: windows.BOOLEAN,
2606) RenameError!void {
2607 return windows.RenameFile(old_dir_fd, old_path_w, new_dir_fd, new_path_w, ReplaceIfExists != 0);
2608}
2609
2610986/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2611987/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
2612988/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
......@@ -2651,7 +1027,7 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDir
26511027 }
26521028}
26531029
2654pub const MakeDirError = std.Io.Dir.MakeError;
1030pub const MakeDirError = std.Io.Dir.CreateDirError;
26551031
26561032/// Create a directory.
26571033/// `mode` is ignored on Windows and WASI.
......@@ -2705,7 +1081,7 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
27051081pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
27061082 _ = mode;
27071083 const sub_dir_handle = windows.OpenFile(dir_path_w, .{
2708 .dir = fs.cwd().fd,
1084 .dir = Io.Dir.cwd().handle,
27091085 .access_mask = .{
27101086 .STANDARD = .{ .SYNCHRONIZE = true },
27111087 .GENERIC = .{ .READ = true },
......@@ -2723,84 +1099,6 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
27231099 windows.CloseHandle(sub_dir_handle);
27241100}
27251101
2726pub const DeleteDirError = error{
2727 AccessDenied,
2728 PermissionDenied,
2729 FileBusy,
2730 SymLinkLoop,
2731 NameTooLong,
2732 FileNotFound,
2733 SystemResources,
2734 NotDir,
2735 DirNotEmpty,
2736 ReadOnlyFileSystem,
2737 /// WASI: file paths must be valid UTF-8.
2738 /// Windows: file paths provided by the user must be valid WTF-8.
2739 /// https://wtf-8.codeberg.page/
2740 BadPathName,
2741 /// On Windows, `\\server` or `\\server\share` was not found.
2742 NetworkNotFound,
2743} || UnexpectedError;
2744
2745/// Deletes an empty directory.
2746/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2747/// On WASI, `dir_path` should be encoded as valid UTF-8.
2748/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
2749pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
2750 if (native_os == .wasi and !builtin.link_libc) {
2751 return unlinkat(AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
2752 error.FileSystem => unreachable, // only occurs when targeting files
2753 error.IsDir => unreachable, // only occurs when targeting files
2754 else => |e| return e,
2755 };
2756 } else if (native_os == .windows) {
2757 const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path);
2758 return rmdirW(dir_path_w.span());
2759 } else {
2760 const dir_path_c = try toPosixPath(dir_path);
2761 return rmdirZ(&dir_path_c);
2762 }
2763}
2764
2765/// Same as `rmdir` except the parameter is null-terminated.
2766/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2767/// On WASI, `dir_path` should be encoded as valid UTF-8.
2768/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
2769pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
2770 if (native_os == .windows) {
2771 const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
2772 return rmdirW(dir_path_w.span());
2773 } else if (native_os == .wasi and !builtin.link_libc) {
2774 return rmdir(mem.sliceTo(dir_path, 0));
2775 }
2776 switch (errno(system.rmdir(dir_path))) {
2777 .SUCCESS => return,
2778 .ACCES => return error.AccessDenied,
2779 .PERM => return error.PermissionDenied,
2780 .BUSY => return error.FileBusy,
2781 .FAULT => unreachable,
2782 .INVAL => return error.BadPathName,
2783 .LOOP => return error.SymLinkLoop,
2784 .NAMETOOLONG => return error.NameTooLong,
2785 .NOENT => return error.FileNotFound,
2786 .NOMEM => return error.SystemResources,
2787 .NOTDIR => return error.NotDir,
2788 .EXIST => return error.DirNotEmpty,
2789 .NOTEMPTY => return error.DirNotEmpty,
2790 .ROFS => return error.ReadOnlyFileSystem,
2791 .ILSEQ => return error.BadPathName,
2792 else => |err| return unexpectedErrno(err),
2793 }
2794}
2795
2796/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded.
2797pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
2798 return windows.DeleteFile(dir_path_w, .{ .dir = fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
2799 error.IsDir => unreachable,
2800 else => |e| return e,
2801 };
2802}
2803
28041102pub const ChangeCurDirError = error{
28051103 AccessDenied,
28061104 FileSystem,
......@@ -2821,11 +1119,9 @@ pub const ChangeCurDirError = error{
28211119/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
28221120pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
28231121 if (native_os == .wasi and !builtin.link_libc) {
2824 @compileError("WASI does not support os.chdir");
1122 @compileError("unsupported OS");
28251123 } else if (native_os == .windows) {
2826 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
2827 const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path);
2828 return chdirW(wtf16_dir_path[0..len]);
1124 @compileError("unsupported OS");
28291125 } else {
28301126 const dir_path_c = try toPosixPath(dir_path);
28311127 return chdirZ(&dir_path_c);
......@@ -2838,12 +1134,9 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
28381134/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
28391135pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
28401136 if (native_os == .windows) {
2841 const dir_path_span = mem.span(dir_path);
2842 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
2843 const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span);
2844 return chdirW(wtf16_dir_path[0..len]);
1137 @compileError("unsupported OS");
28451138 } else if (native_os == .wasi and !builtin.link_libc) {
2846 return chdir(mem.span(dir_path));
1139 @compileError("unsupported OS");
28471140 }
28481141 switch (errno(system.chdir(dir_path))) {
28491142 .SUCCESS => return,
......@@ -2860,14 +1153,6 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
28601153 }
28611154}
28621155
2863/// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded.
2864pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void {
2865 windows.SetCurrentDirectory(dir_path) catch |err| switch (err) {
2866 error.NoDevice => return error.FileSystem,
2867 else => |e| return e,
2868 };
2869}
2870
28711156pub const FchdirError = error{
28721157 AccessDenied,
28731158 NotDir,
......@@ -2889,194 +1174,6 @@ pub fn fchdir(dirfd: fd_t) FchdirError!void {
28891174 }
28901175}
28911176
2892pub const ReadLinkError = error{
2893 /// In WASI, this error may occur when the file descriptor does
2894 /// not hold the required rights to read value of a symbolic link relative to it.
2895 AccessDenied,
2896 PermissionDenied,
2897 FileSystem,
2898 SymLinkLoop,
2899 NameTooLong,
2900 FileNotFound,
2901 SystemResources,
2902 NotLink,
2903 NotDir,
2904 /// WASI: file paths must be valid UTF-8.
2905 /// Windows: file paths provided by the user must be valid WTF-8.
2906 /// https://wtf-8.codeberg.page/
2907 BadPathName,
2908 /// Windows-only. This error may occur if the opened reparse point is
2909 /// of unsupported type.
2910 UnsupportedReparsePointType,
2911 /// On Windows, `\\server` or `\\server\share` was not found.
2912 NetworkNotFound,
2913 /// On Windows, antivirus software is enabled by default. It can be
2914 /// disabled, but Windows Update sometimes ignores the user's preference
2915 /// and re-enables it. When enabled, antivirus software on Windows
2916 /// intercepts file system operations and makes them significantly slower
2917 /// in addition to possibly failing with this error code.
2918 AntivirusInterference,
2919} || UnexpectedError;
2920
2921/// Read value of a symbolic link.
2922/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2923/// On WASI, `file_path` should be encoded as valid UTF-8.
2924/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
2925/// The return value is a slice of `out_buffer` from index 0.
2926/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
2927/// On WASI, the result is encoded as UTF-8.
2928/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
2929pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2930 if (native_os == .wasi and !builtin.link_libc) {
2931 return readlinkat(AT.FDCWD, file_path, out_buffer);
2932 } else if (native_os == .windows) {
2933 var file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
2934 const result_w = try readlinkW(file_path_w.span(), &file_path_w.data);
2935
2936 const len = std.unicode.calcWtf8Len(result_w);
2937 if (len > out_buffer.len) return error.NameTooLong;
2938
2939 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
2940 return out_buffer[0..end_index];
2941 } else {
2942 const file_path_c = try toPosixPath(file_path);
2943 return readlinkZ(&file_path_c, out_buffer);
2944 }
2945}
2946
2947/// Windows-only. Same as `readlink` except `file_path` is WTF-16 LE encoded, NT-prefixed.
2948/// The result is encoded as WTF-16 LE.
2949///
2950/// `file_path` will never be accessed after `out_buffer` has been written to, so it
2951/// is safe to reuse a single buffer for both.
2952///
2953/// See also `readlinkZ`.
2954pub fn readlinkW(file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
2955 return windows.ReadLink(fs.cwd().fd, file_path, out_buffer);
2956}
2957
2958/// Same as `readlink` except `file_path` is null-terminated.
2959pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
2960 if (native_os == .windows) {
2961 var file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
2962 const result_w = try readlinkW(file_path_w.span(), &file_path_w.data);
2963
2964 const len = std.unicode.calcWtf8Len(result_w);
2965 if (len > out_buffer.len) return error.NameTooLong;
2966
2967 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
2968 return out_buffer[0..end_index];
2969 } else if (native_os == .wasi and !builtin.link_libc) {
2970 return readlink(mem.sliceTo(file_path, 0), out_buffer);
2971 }
2972 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
2973 switch (errno(rc)) {
2974 .SUCCESS => return out_buffer[0..@bitCast(rc)],
2975 .ACCES => return error.AccessDenied,
2976 .FAULT => unreachable,
2977 .INVAL => return error.NotLink,
2978 .IO => return error.FileSystem,
2979 .LOOP => return error.SymLinkLoop,
2980 .NAMETOOLONG => return error.NameTooLong,
2981 .NOENT => return error.FileNotFound,
2982 .NOMEM => return error.SystemResources,
2983 .NOTDIR => return error.NotDir,
2984 .ILSEQ => return error.BadPathName,
2985 else => |err| return unexpectedErrno(err),
2986 }
2987}
2988
2989/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle.
2990/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2991/// On WASI, `file_path` should be encoded as valid UTF-8.
2992/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
2993/// The return value is a slice of `out_buffer` from index 0.
2994/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
2995/// On WASI, the result is encoded as UTF-8.
2996/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
2997/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
2998pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2999 if (native_os == .wasi and !builtin.link_libc) {
3000 return readlinkatWasi(dirfd, file_path, out_buffer);
3001 }
3002 if (native_os == .windows) {
3003 var file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
3004 const result_w = try readlinkatW(dirfd, file_path_w.span(), &file_path_w.data);
3005
3006 const len = std.unicode.calcWtf8Len(result_w);
3007 if (len > out_buffer.len) return error.NameTooLong;
3008
3009 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
3010 return out_buffer[0..end_index];
3011 }
3012 const file_path_c = try toPosixPath(file_path);
3013 return readlinkatZ(dirfd, &file_path_c, out_buffer);
3014}
3015
3016/// WASI-only. Same as `readlinkat` but targets WASI.
3017/// See also `readlinkat`.
3018pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
3019 var bufused: usize = undefined;
3020 switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) {
3021 .SUCCESS => return out_buffer[0..bufused],
3022 .ACCES => return error.AccessDenied,
3023 .FAULT => unreachable,
3024 .INVAL => return error.NotLink,
3025 .IO => return error.FileSystem,
3026 .LOOP => return error.SymLinkLoop,
3027 .NAMETOOLONG => return error.NameTooLong,
3028 .NOENT => return error.FileNotFound,
3029 .NOMEM => return error.SystemResources,
3030 .NOTDIR => return error.NotDir,
3031 .NOTCAPABLE => return error.AccessDenied,
3032 .ILSEQ => return error.BadPathName,
3033 else => |err| return unexpectedErrno(err),
3034 }
3035}
3036
3037/// Windows-only. Same as `readlinkat` except `file_path` WTF16 LE encoded, NT-prefixed.
3038/// The result is encoded as WTF-16 LE.
3039///
3040/// `file_path` will never be accessed after `out_buffer` has been written to, so it
3041/// is safe to reuse a single buffer for both.
3042///
3043/// See also `readlinkat`.
3044pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
3045 return windows.ReadLink(dirfd, file_path, out_buffer);
3046}
3047
3048/// Same as `readlinkat` except `file_path` is null-terminated.
3049/// See also `readlinkat`.
3050pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
3051 if (native_os == .windows) {
3052 var file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path);
3053 const result_w = try readlinkatW(dirfd, file_path_w.span(), &file_path_w.data);
3054
3055 const len = std.unicode.calcWtf8Len(result_w);
3056 if (len > out_buffer.len) return error.NameTooLong;
3057
3058 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
3059 return out_buffer[0..end_index];
3060 } else if (native_os == .wasi and !builtin.link_libc) {
3061 return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
3062 }
3063 const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
3064 switch (errno(rc)) {
3065 .SUCCESS => return out_buffer[0..@bitCast(rc)],
3066 .ACCES => return error.AccessDenied,
3067 .FAULT => unreachable,
3068 .INVAL => return error.NotLink,
3069 .IO => return error.FileSystem,
3070 .LOOP => return error.SymLinkLoop,
3071 .NAMETOOLONG => return error.NameTooLong,
3072 .NOENT => return error.FileNotFound,
3073 .NOMEM => return error.SystemResources,
3074 .NOTDIR => return error.NotDir,
3075 .ILSEQ => return error.BadPathName,
3076 else => |err| return unexpectedErrno(err),
3077 }
3078}
3079
30801177pub const SetEidError = error{
30811178 InvalidUserId,
30821179 PermissionDenied,
......@@ -3176,47 +1273,6 @@ pub fn getegid() gid_t {
31761273 return system.getegid();
31771274}
31781275
3179/// Test whether a file descriptor refers to a terminal.
3180pub fn isatty(handle: fd_t) bool {
3181 if (native_os == .windows) {
3182 if (fs.File.isCygwinPty(.{ .handle = handle }))
3183 return true;
3184
3185 var out: windows.DWORD = undefined;
3186 return windows.kernel32.GetConsoleMode(handle, &out) != 0;
3187 }
3188 if (builtin.link_libc) {
3189 return system.isatty(handle) != 0;
3190 }
3191 if (native_os == .wasi) {
3192 var statbuf: wasi.fdstat_t = undefined;
3193 const err = wasi.fd_fdstat_get(handle, &statbuf);
3194 if (err != .SUCCESS)
3195 return false;
3196
3197 // A tty is a character device that we can't seek or tell on.
3198 if (statbuf.fs_filetype != .CHARACTER_DEVICE)
3199 return false;
3200 if (statbuf.fs_rights_base.FD_SEEK or statbuf.fs_rights_base.FD_TELL)
3201 return false;
3202
3203 return true;
3204 }
3205 if (native_os == .linux) {
3206 while (true) {
3207 var wsz: winsize = undefined;
3208 const fd: usize = @bitCast(@as(isize, handle));
3209 const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
3210 switch (linux.errno(rc)) {
3211 .SUCCESS => return true,
3212 .INTR => continue,
3213 else => return false,
3214 }
3215 }
3216 }
3217 return system.isatty(handle) != 0;
3218}
3219
32201276pub const SocketError = error{
32211277 /// Permission to create a socket of the specified type and/or
32221278 /// pro‐tocol is denied.
......@@ -3406,7 +1462,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi
34061462
34071463pub const ListenError = error{
34081464 FileDescriptorNotASocket,
3409 OperationNotSupported,
1465 OperationUnsupported,
34101466} || std.Io.net.IpAddress.ListenError || std.Io.net.UnixAddress.ListenError;
34111467
34121468pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
......@@ -3419,7 +1475,7 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
34191475 .ADDRINUSE => return error.AddressInUse,
34201476 .BADF => unreachable,
34211477 .NOTSOCK => return error.FileDescriptorNotASocket,
3422 .OPNOTSUPP => return error.OperationNotSupported,
1478 .OPNOTSUPP => return error.OperationUnsupported,
34231479 else => |err| return unexpectedErrno(err),
34241480 }
34251481 }
......@@ -4081,7 +2137,7 @@ pub const FanotifyMarkError = error{
40812137 SystemResources,
40822138 UserMarkQuotaExceeded,
40832139 NotDir,
4084 OperationNotSupported,
2140 OperationUnsupported,
40852141 PermissionDenied,
40862142 NotSameFileSystem,
40872143 NameTooLong,
......@@ -4121,7 +2177,7 @@ pub fn fanotify_markZ(
41212177 .NOMEM => return error.SystemResources,
41222178 .NOSPC => return error.UserMarkQuotaExceeded,
41232179 .NOTDIR => return error.NotDir,
4124 .OPNOTSUPP => return error.OperationNotSupported,
2180 .OPNOTSUPP => return error.OperationUnsupported,
41252181 .PERM => return error.PermissionDenied,
41262182 .XDEV => return error.NotSameFileSystem,
41272183 else => |err| return unexpectedErrno(err),
......@@ -4370,75 +2426,14 @@ pub const MSyncError = error{
43702426 UnmappedMemory,
43712427 PermissionDenied,
43722428} || UnexpectedError;
4373
4374pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void {
4375 switch (errno(system.msync(memory.ptr, memory.len, flags))) {
4376 .SUCCESS => return,
4377 .PERM => return error.PermissionDenied,
4378 .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory
4379 .INVAL => unreachable, // Invalid parameters.
4380 else => unreachable,
4381 }
4382}
4383
4384pub const AccessError = error{
4385 AccessDenied,
4386 PermissionDenied,
4387 FileNotFound,
4388 NameTooLong,
4389 InputOutput,
4390 SystemResources,
4391 FileBusy,
4392 SymLinkLoop,
4393 ReadOnlyFileSystem,
4394 /// WASI: file paths must be valid UTF-8.
4395 /// Windows: file paths provided by the user must be valid WTF-8.
4396 /// https://wtf-8.codeberg.page/
4397 BadPathName,
4398 Canceled,
4399} || UnexpectedError;
4400
4401/// check user's permissions for a file
4402///
4403/// * On Windows, asserts `path` is valid [WTF-8](https://wtf-8.codeberg.page/).
4404/// * On WASI, invalid UTF-8 passed to `path` causes `error.BadPathName`.
4405/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
4406///
4407/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by
4408/// Windows. See `fs` for the cross-platform file system API.
4409pub fn access(path: []const u8, mode: u32) AccessError!void {
4410 if (native_os == .windows) {
4411 @compileError("use std.Io instead");
4412 } else if (native_os == .wasi and !builtin.link_libc) {
4413 @compileError("wasi doesn't support absolute paths");
4414 }
4415 const path_c = try toPosixPath(path);
4416 return accessZ(&path_c, mode);
4417}
4418
4419/// Same as `access` except `path` is null-terminated.
4420pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
4421 if (native_os == .windows) {
4422 @compileError("use std.Io instead");
4423 } else if (native_os == .wasi and !builtin.link_libc) {
4424 return access(mem.sliceTo(path, 0), mode);
4425 }
4426 switch (errno(system.access(path, mode))) {
4427 .SUCCESS => return,
4428 .ACCES => return error.AccessDenied,
4429 .PERM => return error.PermissionDenied,
4430 .ROFS => return error.ReadOnlyFileSystem,
4431 .LOOP => return error.SymLinkLoop,
4432 .TXTBSY => return error.FileBusy,
4433 .NOTDIR => return error.FileNotFound,
4434 .NOENT => return error.FileNotFound,
4435 .NAMETOOLONG => return error.NameTooLong,
4436 .INVAL => unreachable,
4437 .FAULT => unreachable,
4438 .IO => return error.InputOutput,
4439 .NOMEM => return error.SystemResources,
4440 .ILSEQ => return error.BadPathName,
4441 else => |err| return unexpectedErrno(err),
2429
2430pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void {
2431 switch (errno(system.msync(memory.ptr, memory.len, flags))) {
2432 .SUCCESS => return,
2433 .PERM => return error.PermissionDenied,
2434 .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory
2435 .INVAL => unreachable, // Invalid parameters.
2436 else => unreachable,
44422437 }
44432438}
44442439
......@@ -4586,177 +2581,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
45862581 }
45872582}
45882583
4589pub const SeekError = std.Io.File.SeekError;
4590
4591pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
4592 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
4593 var result: u64 = undefined;
4594 switch (errno(system.llseek(fd, offset, &result, SEEK.SET))) {
4595 .SUCCESS => return,
4596 .BADF => unreachable, // always a race condition
4597 .INVAL => return error.Unseekable,
4598 .OVERFLOW => return error.Unseekable,
4599 .SPIPE => return error.Unseekable,
4600 .NXIO => return error.Unseekable,
4601 else => |err| return unexpectedErrno(err),
4602 }
4603 }
4604 if (native_os == .windows) {
4605 return windows.SetFilePointerEx_BEGIN(fd, offset);
4606 }
4607 if (native_os == .wasi and !builtin.link_libc) {
4608 var new_offset: wasi.filesize_t = undefined;
4609 switch (wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
4610 .SUCCESS => return,
4611 .BADF => unreachable, // always a race condition
4612 .INVAL => return error.Unseekable,
4613 .OVERFLOW => return error.Unseekable,
4614 .SPIPE => return error.Unseekable,
4615 .NXIO => return error.Unseekable,
4616 .NOTCAPABLE => return error.AccessDenied,
4617 else => |err| return unexpectedErrno(err),
4618 }
4619 }
4620
4621 const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
4622 switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.SET))) {
4623 .SUCCESS => return,
4624 .BADF => unreachable, // always a race condition
4625 .INVAL => return error.Unseekable,
4626 .OVERFLOW => return error.Unseekable,
4627 .SPIPE => return error.Unseekable,
4628 .NXIO => return error.Unseekable,
4629 else => |err| return unexpectedErrno(err),
4630 }
4631}
4632
4633/// Repositions read/write file offset relative to the current offset.
4634pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void {
4635 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
4636 var result: u64 = undefined;
4637 switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.CUR))) {
4638 .SUCCESS => return,
4639 .BADF => unreachable, // always a race condition
4640 .INVAL => return error.Unseekable,
4641 .OVERFLOW => return error.Unseekable,
4642 .SPIPE => return error.Unseekable,
4643 .NXIO => return error.Unseekable,
4644 else => |err| return unexpectedErrno(err),
4645 }
4646 }
4647 if (native_os == .windows) {
4648 return windows.SetFilePointerEx_CURRENT(fd, offset);
4649 }
4650 if (native_os == .wasi and !builtin.link_libc) {
4651 var new_offset: wasi.filesize_t = undefined;
4652 switch (wasi.fd_seek(fd, offset, .CUR, &new_offset)) {
4653 .SUCCESS => return,
4654 .BADF => unreachable, // always a race condition
4655 .INVAL => return error.Unseekable,
4656 .OVERFLOW => return error.Unseekable,
4657 .SPIPE => return error.Unseekable,
4658 .NXIO => return error.Unseekable,
4659 .NOTCAPABLE => return error.AccessDenied,
4660 else => |err| return unexpectedErrno(err),
4661 }
4662 }
4663 const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
4664 switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.CUR))) {
4665 .SUCCESS => return,
4666 .BADF => unreachable, // always a race condition
4667 .INVAL => return error.Unseekable,
4668 .OVERFLOW => return error.Unseekable,
4669 .SPIPE => return error.Unseekable,
4670 .NXIO => return error.Unseekable,
4671 else => |err| return unexpectedErrno(err),
4672 }
4673}
4674
4675/// Repositions read/write file offset relative to the end.
4676pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void {
4677 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
4678 var result: u64 = undefined;
4679 switch (errno(system.llseek(fd, @bitCast(offset), &result, SEEK.END))) {
4680 .SUCCESS => return,
4681 .BADF => unreachable, // always a race condition
4682 .INVAL => return error.Unseekable,
4683 .OVERFLOW => return error.Unseekable,
4684 .SPIPE => return error.Unseekable,
4685 .NXIO => return error.Unseekable,
4686 else => |err| return unexpectedErrno(err),
4687 }
4688 }
4689 if (native_os == .windows) {
4690 return windows.SetFilePointerEx_END(fd, offset);
4691 }
4692 if (native_os == .wasi and !builtin.link_libc) {
4693 var new_offset: wasi.filesize_t = undefined;
4694 switch (wasi.fd_seek(fd, offset, .END, &new_offset)) {
4695 .SUCCESS => return,
4696 .BADF => unreachable, // always a race condition
4697 .INVAL => return error.Unseekable,
4698 .OVERFLOW => return error.Unseekable,
4699 .SPIPE => return error.Unseekable,
4700 .NXIO => return error.Unseekable,
4701 .NOTCAPABLE => return error.AccessDenied,
4702 else => |err| return unexpectedErrno(err),
4703 }
4704 }
4705 const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
4706 switch (errno(lseek_sym(fd, @bitCast(offset), SEEK.END))) {
4707 .SUCCESS => return,
4708 .BADF => unreachable, // always a race condition
4709 .INVAL => return error.Unseekable,
4710 .OVERFLOW => return error.Unseekable,
4711 .SPIPE => return error.Unseekable,
4712 .NXIO => return error.Unseekable,
4713 else => |err| return unexpectedErrno(err),
4714 }
4715}
4716
4717/// Returns the read/write file offset relative to the beginning.
4718pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 {
4719 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
4720 var result: u64 = undefined;
4721 switch (errno(system.llseek(fd, 0, &result, SEEK.CUR))) {
4722 .SUCCESS => return result,
4723 .BADF => unreachable, // always a race condition
4724 .INVAL => return error.Unseekable,
4725 .OVERFLOW => return error.Unseekable,
4726 .SPIPE => return error.Unseekable,
4727 .NXIO => return error.Unseekable,
4728 else => |err| return unexpectedErrno(err),
4729 }
4730 }
4731 if (native_os == .windows) {
4732 return windows.SetFilePointerEx_CURRENT_get(fd);
4733 }
4734 if (native_os == .wasi and !builtin.link_libc) {
4735 var new_offset: wasi.filesize_t = undefined;
4736 switch (wasi.fd_seek(fd, 0, .CUR, &new_offset)) {
4737 .SUCCESS => return new_offset,
4738 .BADF => unreachable, // always a race condition
4739 .INVAL => return error.Unseekable,
4740 .OVERFLOW => return error.Unseekable,
4741 .SPIPE => return error.Unseekable,
4742 .NXIO => return error.Unseekable,
4743 .NOTCAPABLE => return error.AccessDenied,
4744 else => |err| return unexpectedErrno(err),
4745 }
4746 }
4747 const lseek_sym = if (lfs64_abi) system.lseek64 else system.lseek;
4748 const rc = lseek_sym(fd, 0, SEEK.CUR);
4749 switch (errno(rc)) {
4750 .SUCCESS => return @bitCast(rc),
4751 .BADF => unreachable, // always a race condition
4752 .INVAL => return error.Unseekable,
4753 .OVERFLOW => return error.Unseekable,
4754 .SPIPE => return error.Unseekable,
4755 .NXIO => return error.Unseekable,
4756 else => |err| return unexpectedErrno(err),
4757 }
4758}
4759
47602584pub const FcntlError = error{
47612585 PermissionDenied,
47622586 FileBusy,
......@@ -4786,185 +2610,6 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {
47862610 }
47872611}
47882612
4789pub const FlockError = error{
4790 WouldBlock,
4791
4792 /// The kernel ran out of memory for allocating file locks
4793 SystemResources,
4794
4795 /// The underlying filesystem does not support file locks
4796 FileLocksNotSupported,
4797} || UnexpectedError;
4798
4799/// Depending on the operating system `flock` may or may not interact with
4800/// `fcntl` locks made by other processes.
4801pub fn flock(fd: fd_t, operation: i32) FlockError!void {
4802 while (true) {
4803 const rc = system.flock(fd, operation);
4804 switch (errno(rc)) {
4805 .SUCCESS => return,
4806 .BADF => unreachable,
4807 .INTR => continue,
4808 .INVAL => unreachable, // invalid parameters
4809 .NOLCK => return error.SystemResources,
4810 .AGAIN => return error.WouldBlock, // TODO: integrate with async instead of just returning an error
4811 .OPNOTSUPP => return error.FileLocksNotSupported,
4812 else => |err| return unexpectedErrno(err),
4813 }
4814 }
4815}
4816
4817pub const RealPathError = error{
4818 FileNotFound,
4819 AccessDenied,
4820 PermissionDenied,
4821 NameTooLong,
4822 NotSupported,
4823 NotDir,
4824 SymLinkLoop,
4825 InputOutput,
4826 FileTooBig,
4827 IsDir,
4828 ProcessFdQuotaExceeded,
4829 SystemFdQuotaExceeded,
4830 NoDevice,
4831 SystemResources,
4832 NoSpaceLeft,
4833 FileSystem,
4834 DeviceBusy,
4835 ProcessNotFound,
4836
4837 SharingViolation,
4838 PipeBusy,
4839
4840 /// Windows: file paths provided by the user must be valid WTF-8.
4841 /// https://wtf-8.codeberg.page/
4842 BadPathName,
4843
4844 /// On Windows, `\\server` or `\\server\share` was not found.
4845 NetworkNotFound,
4846
4847 PathAlreadyExists,
4848
4849 /// On Windows, antivirus software is enabled by default. It can be
4850 /// disabled, but Windows Update sometimes ignores the user's preference
4851 /// and re-enables it. When enabled, antivirus software on Windows
4852 /// intercepts file system operations and makes them significantly slower
4853 /// in addition to possibly failing with this error code.
4854 AntivirusInterference,
4855
4856 /// On Windows, the volume does not contain a recognized file system. File
4857 /// system drivers might not be loaded, or the volume may be corrupt.
4858 UnrecognizedVolume,
4859
4860 Canceled,
4861} || UnexpectedError;
4862
4863/// Return the canonicalized absolute pathname.
4864///
4865/// Expands all symbolic links and resolves references to `.`, `..`, and
4866/// extra `/` characters in `pathname`.
4867///
4868/// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
4869///
4870/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
4871///
4872/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
4873///
4874/// See also `realpathZ` and `realpathW`.
4875///
4876/// * On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
4877/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding.
4878///
4879/// Calling this function is usually a bug.
4880pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
4881 if (native_os == .windows) {
4882 var pathname_w = try windows.sliceToPrefixedFileW(null, pathname);
4883
4884 const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data);
4885
4886 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
4887 return out_buffer[0..end_index];
4888 } else if (native_os == .wasi and !builtin.link_libc) {
4889 @compileError("WASI does not support os.realpath");
4890 }
4891 const pathname_c = try toPosixPath(pathname);
4892 return realpathZ(&pathname_c, out_buffer);
4893}
4894
4895/// Same as `realpath` except `pathname` is null-terminated.
4896///
4897/// Calling this function is usually a bug.
4898pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
4899 if (native_os == .windows) {
4900 var pathname_w = try windows.cStrToPrefixedFileW(null, pathname);
4901
4902 const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data);
4903
4904 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
4905 return out_buffer[0..end_index];
4906 } else if (native_os == .wasi and !builtin.link_libc) {
4907 return realpath(mem.sliceTo(pathname, 0), out_buffer);
4908 }
4909 if (!builtin.link_libc) {
4910 const flags: O = switch (native_os) {
4911 .linux => .{
4912 .NONBLOCK = true,
4913 .CLOEXEC = true,
4914 .PATH = true,
4915 },
4916 else => .{
4917 .NONBLOCK = true,
4918 .CLOEXEC = true,
4919 },
4920 };
4921 const fd = openZ(pathname, flags, 0) catch |err| switch (err) {
4922 error.FileLocksNotSupported => unreachable,
4923 error.WouldBlock => unreachable,
4924 error.FileBusy => unreachable, // not asking for write permissions
4925 else => |e| return e,
4926 };
4927 defer close(fd);
4928
4929 return std.os.getFdPath(fd, out_buffer);
4930 }
4931 const result_path = std.c.realpath(pathname, out_buffer) orelse switch (@as(E, @enumFromInt(std.c._errno().*))) {
4932 .SUCCESS => unreachable,
4933 .INVAL => unreachable,
4934 .BADF => unreachable,
4935 .FAULT => unreachable,
4936 .ACCES => return error.AccessDenied,
4937 .NOENT => return error.FileNotFound,
4938 .OPNOTSUPP => return error.NotSupported,
4939 .NOTDIR => return error.NotDir,
4940 .NAMETOOLONG => return error.NameTooLong,
4941 .LOOP => return error.SymLinkLoop,
4942 .IO => return error.InputOutput,
4943 else => |err| return unexpectedErrno(err),
4944 };
4945 return mem.sliceTo(result_path, 0);
4946}
4947
4948/// Deprecated: use `realpathW2`.
4949///
4950/// Same as `realpath` except `pathname` is WTF16LE-encoded.
4951///
4952/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
4953///
4954/// Calling this function is usually a bug.
4955pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
4956 return fs.cwd().realpathW(pathname, out_buffer);
4957}
4958
4959/// Same as `realpath` except `pathname` is WTF16LE-encoded.
4960///
4961/// The result is encoded as WTF16LE.
4962///
4963/// Calling this function is usually a bug.
4964pub fn realpathW2(pathname: []const u16, out_buffer: *[std.os.windows.PATH_MAX_WIDE]u16) RealPathError![]u16 {
4965 return fs.cwd().realpathW2(pathname, out_buffer);
4966}
4967
49682613/// Spurious wakeups are possible and no precision of timing is guaranteed.
49692614pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
49702615 var req = timespec{
......@@ -5049,16 +2694,16 @@ pub fn dl_iterate_phdr(
50492694
50502695 // Last return value from the callback function.
50512696 while (it.next()) |entry| {
5052 const phdrs: []elf.ElfN.Phdr = if (entry.l_addr != 0) phdrs: {
5053 const ehdr: *elf.ElfN.Ehdr = @ptrFromInt(entry.l_addr);
2697 const phdrs: []elf.ElfN.Phdr = if (entry.addr != 0) phdrs: {
2698 const ehdr: *elf.ElfN.Ehdr = @ptrFromInt(entry.addr);
50542699 assert(mem.eql(u8, ehdr.ident[0..4], elf.MAGIC));
5055 const phdrs: [*]elf.ElfN.Phdr = @ptrFromInt(entry.l_addr + ehdr.phoff);
2700 const phdrs: [*]elf.ElfN.Phdr = @ptrFromInt(entry.addr + ehdr.phoff);
50562701 break :phdrs phdrs[0..ehdr.phnum];
50572702 } else getSelfPhdrs();
50582703
50592704 var info: dl_phdr_info = .{
5060 .addr = entry.l_addr,
5061 .name = entry.l_name,
2705 .addr = entry.addr,
2706 .name = entry.name,
50622707 .phdr = phdrs.ptr,
50632708 .phnum = @intCast(phdrs.len),
50642709 };
......@@ -5229,74 +2874,6 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*
52292874 }
52302875}
52312876
5232pub const FutimensError = error{
5233 /// times is NULL, or both nsec values are UTIME_NOW, and either:
5234 /// * the effective user ID of the caller does not match the owner
5235 /// of the file, the caller does not have write access to the
5236 /// file, and the caller is not privileged (Linux: does not have
5237 /// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability);
5238 /// or,
5239 /// * the file is marked immutable (see chattr(1)).
5240 AccessDenied,
5241
5242 /// The caller attempted to change one or both timestamps to a value
5243 /// other than the current time, or to change one of the timestamps
5244 /// to the current time while leaving the other timestamp unchanged,
5245 /// (i.e., times is not NULL, neither nsec field is UTIME_NOW,
5246 /// and neither nsec field is UTIME_OMIT) and either:
5247 /// * the caller's effective user ID does not match the owner of
5248 /// file, and the caller is not privileged (Linux: does not have
5249 /// the CAP_FOWNER capability); or,
5250 /// * the file is marked append-only or immutable (see chattr(1)).
5251 PermissionDenied,
5252
5253 ReadOnlyFileSystem,
5254} || UnexpectedError;
5255
5256pub fn futimens(fd: fd_t, times: ?*const [2]timespec) FutimensError!void {
5257 if (native_os == .wasi and !builtin.link_libc) {
5258 // TODO WASI encodes `wasi.fstflags` to signify magic values
5259 // similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore
5260 // this here, but we should really handle it somehow.
5261 const error_code = blk: {
5262 if (times) |times_arr| {
5263 const atim = times_arr[0].toTimestamp();
5264 const mtim = times_arr[1].toTimestamp();
5265 break :blk wasi.fd_filestat_set_times(fd, atim, mtim, .{
5266 .ATIM = true,
5267 .MTIM = true,
5268 });
5269 }
5270
5271 break :blk wasi.fd_filestat_set_times(fd, 0, 0, .{
5272 .ATIM_NOW = true,
5273 .MTIM_NOW = true,
5274 });
5275 };
5276 switch (error_code) {
5277 .SUCCESS => return,
5278 .ACCES => return error.AccessDenied,
5279 .PERM => return error.PermissionDenied,
5280 .BADF => unreachable, // always a race condition
5281 .FAULT => unreachable,
5282 .INVAL => unreachable,
5283 .ROFS => return error.ReadOnlyFileSystem,
5284 else => |err| return unexpectedErrno(err),
5285 }
5286 }
5287
5288 switch (errno(system.futimens(fd, times))) {
5289 .SUCCESS => return,
5290 .ACCES => return error.AccessDenied,
5291 .PERM => return error.PermissionDenied,
5292 .BADF => unreachable, // always a race condition
5293 .FAULT => unreachable,
5294 .INVAL => unreachable,
5295 .ROFS => return error.ReadOnlyFileSystem,
5296 else => |err| return unexpectedErrno(err),
5297 }
5298}
5299
53002877pub const GetHostNameError = error{PermissionDenied} || UnexpectedError;
53012878
53022879pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {
......@@ -5612,98 +3189,6 @@ pub fn send(
56123189 };
56133190}
56143191
5615pub const CopyFileRangeError = error{
5616 FileTooBig,
5617 InputOutput,
5618 /// `fd_in` is not open for reading; or `fd_out` is not open for writing;
5619 /// or the `APPEND` flag is set for `fd_out`.
5620 FilesOpenedWithWrongFlags,
5621 IsDir,
5622 OutOfMemory,
5623 NoSpaceLeft,
5624 Unseekable,
5625 PermissionDenied,
5626 SwapFile,
5627 CorruptedData,
5628} || PReadError || PWriteError || UnexpectedError;
5629
5630/// Transfer data between file descriptors at specified offsets.
5631///
5632/// Returns the number of bytes written, which can less than requested.
5633///
5634/// The `copy_file_range` call copies `len` bytes from one file descriptor to another. When possible,
5635/// this is done within the operating system kernel, which can provide better performance
5636/// characteristics than transferring data from kernel to user space and back, such as with
5637/// `pread` and `pwrite` calls.
5638///
5639/// `fd_in` must be a file descriptor opened for reading, and `fd_out` must be a file descriptor
5640/// opened for writing. They may be any kind of file descriptor; however, if `fd_in` is not a regular
5641/// file system file, it may cause this function to fall back to calling `pread` and `pwrite`, in which case
5642/// atomicity guarantees no longer apply.
5643///
5644/// If `fd_in` and `fd_out` are the same, source and target ranges must not overlap.
5645/// The file descriptor seek positions are ignored and not updated.
5646/// When `off_in` is past the end of the input file, it successfully reads 0 bytes.
5647///
5648/// `flags` has different meanings per operating system; refer to the respective man pages.
5649///
5650/// These systems support in-kernel data copying:
5651/// * Linux (cross-filesystem from version 5.3)
5652/// * FreeBSD 13.0
5653///
5654/// Other systems fall back to calling `pread` / `pwrite`.
5655///
5656/// Maximum offsets on Linux and FreeBSD are `maxInt(i64)`.
5657pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize {
5658 if (builtin.os.tag == .freebsd or builtin.os.tag == .linux) {
5659 const use_c = native_os != .linux or
5660 std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 34, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 27, .patch = 0 });
5661 const sys = if (use_c) std.c else linux;
5662
5663 var off_in_copy: i64 = @bitCast(off_in);
5664 var off_out_copy: i64 = @bitCast(off_out);
5665
5666 while (true) {
5667 const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags);
5668 if (native_os == .freebsd) {
5669 switch (sys.errno(rc)) {
5670 .SUCCESS => return @intCast(rc),
5671 .BADF => return error.FilesOpenedWithWrongFlags,
5672 .FBIG => return error.FileTooBig,
5673 .IO => return error.InputOutput,
5674 .ISDIR => return error.IsDir,
5675 .NOSPC => return error.NoSpaceLeft,
5676 .INVAL => break, // these may not be regular files, try fallback
5677 .INTEGRITY => return error.CorruptedData,
5678 .INTR => continue,
5679 else => |err| return unexpectedErrno(err),
5680 }
5681 } else { // assume linux
5682 switch (sys.errno(rc)) {
5683 .SUCCESS => return @intCast(rc),
5684 .BADF => return error.FilesOpenedWithWrongFlags,
5685 .FBIG => return error.FileTooBig,
5686 .IO => return error.InputOutput,
5687 .ISDIR => return error.IsDir,
5688 .NOSPC => return error.NoSpaceLeft,
5689 .INVAL => break, // these may not be regular files, try fallback
5690 .NOMEM => return error.OutOfMemory,
5691 .OVERFLOW => return error.Unseekable,
5692 .PERM => return error.PermissionDenied,
5693 .TXTBSY => return error.SwapFile,
5694 .XDEV => break, // support for cross-filesystem copy added in Linux 5.3, use fallback
5695 else => |err| return unexpectedErrno(err),
5696 }
5697 }
5698 }
5699 }
5700
5701 var buf: [8 * 4096]u8 = undefined;
5702 const amt_read = try pread(fd_in, buf[0..@min(buf.len, len)], off_in);
5703 if (amt_read == 0) return 0;
5704 return pwrite(fd_out, buf[0..amt_read], off_out);
5705}
5706
57073192pub const PollError = error{
57083193 /// The network subsystem has failed.
57093194 NetworkDown,
......@@ -5916,7 +3401,7 @@ pub const SetSockOptError = error{
59163401 /// Setting the socket option requires more elevated permissions.
59173402 PermissionDenied,
59183403
5919 OperationNotSupported,
3404 OperationUnsupported,
59203405 NetworkDown,
59213406 FileDescriptorNotASocket,
59223407 SocketNotBound,
......@@ -5952,7 +3437,7 @@ pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSo
59523437 .NOBUFS => return error.SystemResources,
59533438 .PERM => return error.PermissionDenied,
59543439 .NODEV => return error.NoDevice,
5955 .OPNOTSUPP => return error.OperationNotSupported,
3440 .OPNOTSUPP => return error.OperationUnsupported,
59563441 else => |err| return unexpectedErrno(err),
59573442 }
59583443 }
......@@ -6118,12 +3603,7 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t {
61183603 }
61193604}
61203605
6121pub const SyncError = error{
6122 InputOutput,
6123 NoSpaceLeft,
6124 DiskQuota,
6125 AccessDenied,
6126} || UnexpectedError;
3606pub const SyncError = std.Io.File.SyncError;
61273607
61283608/// Write all pending file contents and metadata modifications to all filesystems.
61293609pub fn sync() void {
......@@ -6143,38 +3623,8 @@ pub fn syncfs(fd: fd_t) SyncError!void {
61433623 }
61443624}
61453625
6146/// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem.
6147pub fn fsync(fd: fd_t) SyncError!void {
6148 if (native_os == .windows) {
6149 if (windows.kernel32.FlushFileBuffers(fd) != 0)
6150 return;
6151 switch (windows.GetLastError()) {
6152 .SUCCESS => return,
6153 .INVALID_HANDLE => unreachable,
6154 .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time
6155 .UNEXP_NET_ERR => return error.InputOutput,
6156 else => return error.InputOutput,
6157 }
6158 }
6159 const rc = system.fsync(fd);
6160 switch (errno(rc)) {
6161 .SUCCESS => return,
6162 .BADF, .INVAL, .ROFS => unreachable,
6163 .IO => return error.InputOutput,
6164 .NOSPC => return error.NoSpaceLeft,
6165 .DQUOT => return error.DiskQuota,
6166 else => |err| return unexpectedErrno(err),
6167 }
6168}
6169
61703626/// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata.
61713627pub fn fdatasync(fd: fd_t) SyncError!void {
6172 if (native_os == .windows) {
6173 return fsync(fd) catch |err| switch (err) {
6174 SyncError.AccessDenied => return, // fdatasync doesn't promise that the access time was synced
6175 else => return err,
6176 };
6177 }
61783628 const rc = system.fdatasync(fd);
61793629 switch (errno(rc)) {
61803630 .SUCCESS => return,
......@@ -6197,7 +3647,7 @@ pub const PrctlError = error{
61973647 /// or PR_MPX_DISABLE_MANAGEMENT
61983648 UnsupportedFeature,
61993649 /// Can only occur with PR_SET_FP_MODE
6200 OperationNotSupported,
3650 OperationUnsupported,
62013651 PermissionDenied,
62023652} || UnexpectedError;
62033653
......@@ -6221,7 +3671,7 @@ pub fn prctl(option: PR, args: anytype) PrctlError!u31 {
62213671 .FAULT => return error.InvalidAddress,
62223672 .INVAL => unreachable,
62233673 .NODEV, .NXIO => return error.UnsupportedFeature,
6224 .OPNOTSUPP => return error.OperationNotSupported,
3674 .OPNOTSUPP => return error.OperationUnsupported,
62253675 .PERM, .BUSY => return error.PermissionDenied,
62263676 .RANGE => unreachable,
62273677 else => |err| return unexpectedErrno(err),
......@@ -6480,7 +3930,7 @@ pub const PtraceError = error{
64803930 DeviceBusy,
64813931 InputOutput,
64823932 NameTooLong,
6483 OperationNotSupported,
3933 OperationUnsupported,
64843934 OutOfMemory,
64853935 ProcessNotFound,
64863936 PermissionDenied,
......@@ -6582,7 +4032,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo
65824032 .INVAL => unreachable,
65834033 .PERM => error.PermissionDenied,
65844034 .BUSY => error.DeviceBusy,
6585 .NOTSUP => error.OperationNotSupported,
4035 .NOTSUP => error.OperationUnsupported,
65864036 else => |err| return unexpectedErrno(err),
65874037 },
65884038
......@@ -6593,7 +4043,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo
65934043pub const NameToFileHandleAtError = error{
65944044 FileNotFound,
65954045 NotDir,
6596 OperationNotSupported,
4046 OperationUnsupported,
65974047 NameTooLong,
65984048 Unexpected,
65994049};
......@@ -6622,7 +4072,7 @@ pub fn name_to_handle_atZ(
66224072 .INVAL => unreachable, // bad flags, or handle_bytes too big
66234073 .NOENT => return error.FileNotFound,
66244074 .NOTDIR => return error.NotDir,
6625 .OPNOTSUPP => return error.OperationNotSupported,
4075 .OPNOTSUPP => return error.OperationUnsupported,
66264076 .OVERFLOW => return error.NameTooLong,
66274077 else => |err| return unexpectedErrno(err),
66284078 }
lib/std/posix/test.zig+84-554
......@@ -1,34 +1,24 @@
1const builtin = @import("builtin");
2const native_os = builtin.target.os.tag;
3const AtomicRmwOp = std.builtin.AtomicRmwOp;
4const AtomicOrder = std.builtin.AtomicOrder;
5
16const std = @import("../std.zig");
7const Io = std.Io;
8const Dir = std.Io.Dir;
29const posix = std.posix;
3const testing = std.testing;
4const expect = testing.expect;
5const expectEqual = testing.expectEqual;
6const expectError = testing.expectError;
7const fs = std.fs;
810const mem = std.mem;
911const elf = std.elf;
1012const linux = std.os.linux;
13const AT = std.posix.AT;
1114
12const a = std.testing.allocator;
13
14const builtin = @import("builtin");
15const AtomicRmwOp = std.builtin.AtomicRmwOp;
16const AtomicOrder = std.builtin.AtomicOrder;
17const native_os = builtin.target.os.tag;
15const testing = std.testing;
16const expect = std.testing.expect;
17const expectEqual = std.testing.expectEqual;
18const expectEqualSlices = std.testing.expectEqualSlices;
19const expectEqualStrings = std.testing.expectEqualStrings;
20const expectError = std.testing.expectError;
1821const tmpDir = std.testing.tmpDir;
19const AT = posix.AT;
20
21// NOTE: several additional tests are in test/standalone/posix/. Any tests that mutate
22// process-wide POSIX state (cwd, signals, etc) cannot be Zig unit tests and should be over there.
23
24// https://github.com/ziglang/zig/issues/20288
25test "WTF-8 to WTF-16 conversion buffer overflows" {
26 if (native_os != .windows) return error.SkipZigTest;
27
28 const input_wtf8 = "\u{10FFFF}" ** 16385;
29 try expectError(error.NameTooLong, posix.chdir(input_wtf8));
30 try expectError(error.NameTooLong, posix.chdirZ(input_wtf8));
31}
3222
3323test "check WASI CWD" {
3424 if (native_os == .wasi) {
......@@ -43,206 +33,6 @@ test "check WASI CWD" {
4333 }
4434}
4535
46test "open smoke test" {
47 if (native_os == .wasi) return error.SkipZigTest;
48 if (native_os == .windows) return error.SkipZigTest;
49 if (native_os == .openbsd) return error.SkipZigTest;
50
51 // TODO verify file attributes using `fstat`
52
53 var tmp = tmpDir(.{});
54 defer tmp.cleanup();
55
56 const base_path = try tmp.dir.realpathAlloc(a, ".");
57 defer a.free(base_path);
58
59 const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
60
61 {
62 // Create some file using `open`.
63 const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
64 defer a.free(file_path);
65 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
66 posix.close(fd);
67 }
68
69 {
70 // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
71 const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
72 defer a.free(file_path);
73 try expectError(error.PathAlreadyExists, posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode));
74 }
75
76 {
77 // Try opening without `EXCL` flag.
78 const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
79 defer a.free(file_path);
80 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true }, mode);
81 posix.close(fd);
82 }
83
84 {
85 // Try opening as a directory which should fail.
86 const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
87 defer a.free(file_path);
88 try expectError(error.NotDir, posix.open(file_path, .{ .ACCMODE = .RDWR, .DIRECTORY = true }, mode));
89 }
90
91 {
92 // Create some directory
93 const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
94 defer a.free(file_path);
95 try posix.mkdir(file_path, mode);
96 }
97
98 {
99 // Open dir using `open`
100 const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
101 defer a.free(file_path);
102 const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);
103 posix.close(fd);
104 }
105
106 {
107 // Try opening as file which should fail.
108 const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
109 defer a.free(file_path);
110 try expectError(error.IsDir, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));
111 }
112}
113
114test "readlink on Windows" {
115 if (native_os != .windows) return error.SkipZigTest;
116
117 try testReadlink("C:\\ProgramData", "C:\\Users\\All Users");
118 try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User");
119 try testReadlink("C:\\Users", "C:\\Documents and Settings");
120}
121
122fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
123 var buffer: [fs.max_path_bytes]u8 = undefined;
124 const given = try posix.readlink(symlink_path, buffer[0..]);
125 try expect(mem.eql(u8, target_path, given));
126}
127
128fn getLinkInfo(fd: posix.fd_t) !struct { posix.ino_t, posix.nlink_t } {
129 if (native_os == .linux) {
130 const stx = try linux.wrapped.statx(
131 fd,
132 "",
133 posix.AT.EMPTY_PATH,
134 .{ .INO = true, .NLINK = true },
135 );
136 std.debug.assert(stx.mask.INO);
137 std.debug.assert(stx.mask.NLINK);
138 return .{ stx.ino, stx.nlink };
139 }
140
141 const st = try posix.fstat(fd);
142 return .{ st.ino, st.nlink };
143}
144
145test "linkat with different directories" {
146 switch (native_os) {
147 .wasi, .linux, .illumos => {},
148 else => return error.SkipZigTest,
149 }
150
151 var tmp = tmpDir(.{});
152 defer tmp.cleanup();
153
154 const target_name = "link-target";
155 const link_name = "newlink";
156
157 const subdir = try tmp.dir.makeOpenPath("subdir", .{});
158
159 defer tmp.dir.deleteFile(target_name) catch {};
160 try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" });
161
162 // Test 1: link from file in subdir back up to target in parent directory
163 try posix.linkat(tmp.dir.fd, target_name, subdir.fd, link_name, 0);
164
165 const efd = try tmp.dir.openFile(target_name, .{});
166 defer efd.close();
167
168 const nfd = try subdir.openFile(link_name, .{});
169 defer nfd.close();
170
171 {
172 const eino, _ = try getLinkInfo(efd.handle);
173 const nino, const nlink = try getLinkInfo(nfd.handle);
174 try testing.expectEqual(eino, nino);
175 try testing.expectEqual(@as(posix.nlink_t, 2), nlink);
176 }
177
178 // Test 2: remove link
179 try posix.unlinkat(subdir.fd, link_name, 0);
180 _, const elink = try getLinkInfo(efd.handle);
181 try testing.expectEqual(@as(posix.nlink_t, 1), elink);
182}
183
184test "fstatat" {
185 if (posix.Stat == void) return error.SkipZigTest;
186 if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest;
187
188 var tmp = tmpDir(.{});
189 defer tmp.cleanup();
190
191 // create dummy file
192 const contents = "nonsense";
193 try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = contents });
194
195 // fetch file's info on the opened fd directly
196 const file = try tmp.dir.openFile("file.txt", .{});
197 const stat = try posix.fstat(file.handle);
198 defer file.close();
199
200 // now repeat but using `fstatat` instead
201 const statat = try posix.fstatat(tmp.dir.fd, "file.txt", posix.AT.SYMLINK_NOFOLLOW);
202
203 try expectEqual(stat.dev, statat.dev);
204 try expectEqual(stat.ino, statat.ino);
205 try expectEqual(stat.nlink, statat.nlink);
206 try expectEqual(stat.mode, statat.mode);
207 try expectEqual(stat.uid, statat.uid);
208 try expectEqual(stat.gid, statat.gid);
209 try expectEqual(stat.rdev, statat.rdev);
210 try expectEqual(stat.size, statat.size);
211 try expectEqual(stat.blksize, statat.blksize);
212 // The stat.blocks/statat.blocks count is managed by the filesystem and may
213 // change if the file is stored in a journal or "inline".
214 // try expectEqual(stat.blocks, statat.blocks);
215}
216
217test "readlinkat" {
218 var tmp = tmpDir(.{});
219 defer tmp.cleanup();
220
221 // create file
222 try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" });
223
224 // create a symbolic link
225 if (native_os == .windows) {
226 std.os.windows.CreateSymbolicLink(
227 tmp.dir.fd,
228 &[_]u16{ 'l', 'i', 'n', 'k' },
229 &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' },
230 false,
231 ) catch |err| switch (err) {
232 // Symlink requires admin privileges on windows, so this test can legitimately fail.
233 error.AccessDenied => return error.SkipZigTest,
234 else => return err,
235 };
236 } else {
237 try posix.symlinkat("file.txt", tmp.dir.fd, "link");
238 }
239
240 // read the link
241 var buffer: [fs.max_path_bytes]u8 = undefined;
242 const read_link = try posix.readlinkat(tmp.dir.fd, "link", buffer[0..]);
243 try expect(mem.eql(u8, "file.txt", read_link));
244}
245
24636test "getrandom" {
24737 var buf_a: [50]u8 = undefined;
24838 var buf_b: [50]u8 = undefined;
......@@ -273,7 +63,7 @@ test "sigaltstack" {
27363 // Setting a stack size less than MINSIGSTKSZ returns ENOMEM
27464 st.flags = 0;
27565 st.size = 1;
276 try testing.expectError(error.SizeTooSmall, posix.sigaltstack(&st, null));
66 try expectError(error.SizeTooSmall, posix.sigaltstack(&st, null));
27767}
27868
27969// If the type is not available use void to avoid erroring out when `iter_fn` is
......@@ -345,7 +135,7 @@ test "pipe" {
345135 try expect((try posix.write(fds[1], "hello")) == 5);
346136 var buf: [16]u8 = undefined;
347137 try expect((try posix.read(fds[0], buf[0..])) == 5);
348 try testing.expectEqualSlices(u8, buf[0..5], "hello");
138 try expectEqualSlices(u8, buf[0..5], "hello");
349139 posix.close(fds[1]);
350140 posix.close(fds[0]);
351141}
......@@ -356,6 +146,8 @@ test "argsAlloc" {
356146}
357147
358148test "memfd_create" {
149 const io = testing.io;
150
359151 // memfd_create is only supported by linux and freebsd.
360152 switch (native_os) {
361153 .linux => {},
......@@ -366,21 +158,22 @@ test "memfd_create" {
366158 else => return error.SkipZigTest,
367159 }
368160
369 const fd = try posix.memfd_create("test", 0);
370 defer posix.close(fd);
371 try expect((try posix.write(fd, "test")) == 4);
372 try posix.lseek_SET(fd, 0);
161 const file: Io.File = .{ .handle = try posix.memfd_create("test", 0) };
162 defer file.close(io);
163 try file.writePositionalAll(io, "test", 0);
373164
374165 var buf: [10]u8 = undefined;
375 const bytes_read = try posix.read(fd, &buf);
166 const bytes_read = try file.readPositionalAll(io, &buf, 0);
376167 try expect(bytes_read == 4);
377 try expect(mem.eql(u8, buf[0..4], "test"));
168 try expectEqualStrings("test", buf[0..4]);
378169}
379170
380171test "mmap" {
381172 if (native_os == .windows or native_os == .wasi)
382173 return error.SkipZigTest;
383174
175 const io = testing.io;
176
384177 var tmp = tmpDir(.{});
385178 defer tmp.cleanup();
386179
......@@ -396,14 +189,14 @@ test "mmap" {
396189 );
397190 defer posix.munmap(data);
398191
399 try testing.expectEqual(@as(usize, 1234), data.len);
192 try expectEqual(@as(usize, 1234), data.len);
400193
401194 // By definition the data returned by mmap is zero-filled
402 try testing.expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234));
195 try expect(mem.eql(u8, data, &[_]u8{0x00} ** 1234));
403196
404197 // Make sure the memory is writeable as requested
405198 @memset(data, 0x55);
406 try testing.expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234));
199 try expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234));
407200 }
408201
409202 const test_out_file = "os_tmp_test";
......@@ -412,10 +205,10 @@ test "mmap" {
412205
413206 // Create a file used for testing mmap() calls with a file descriptor
414207 {
415 const file = try tmp.dir.createFile(test_out_file, .{});
416 defer file.close();
208 const file = try tmp.dir.createFile(io, test_out_file, .{});
209 defer file.close(io);
417210
418 var stream = file.writer(&.{});
211 var stream = file.writer(io, &.{});
419212
420213 var i: usize = 0;
421214 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
......@@ -425,8 +218,8 @@ test "mmap" {
425218
426219 // Map the whole file
427220 {
428 const file = try tmp.dir.openFile(test_out_file, .{});
429 defer file.close();
221 const file = try tmp.dir.openFile(io, test_out_file, .{});
222 defer file.close(io);
430223
431224 const data = try posix.mmap(
432225 null,
......@@ -442,7 +235,7 @@ test "mmap" {
442235
443236 var i: usize = 0;
444237 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
445 try testing.expectEqual(i, try stream.takeInt(u32, .little));
238 try expectEqual(i, try stream.takeInt(u32, .little));
446239 }
447240 }
448241
......@@ -450,8 +243,8 @@ test "mmap" {
450243
451244 // Map the upper half of the file
452245 {
453 const file = try tmp.dir.openFile(test_out_file, .{});
454 defer file.close();
246 const file = try tmp.dir.openFile(io, test_out_file, .{});
247 defer file.close(io);
455248
456249 const data = try posix.mmap(
457250 null,
......@@ -467,7 +260,7 @@ test "mmap" {
467260
468261 var i: usize = alloc_size / 2 / @sizeOf(u32);
469262 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
470 try testing.expectEqual(i, try stream.takeInt(u32, .little));
263 try expectEqual(i, try stream.takeInt(u32, .little));
471264 }
472265 }
473266}
......@@ -476,13 +269,15 @@ test "fcntl" {
476269 if (native_os == .windows or native_os == .wasi)
477270 return error.SkipZigTest;
478271
272 const io = testing.io;
273
479274 var tmp = tmpDir(.{});
480275 defer tmp.cleanup();
481276
482277 const test_out_file = "os_tmp_test";
483278
484 const file = try tmp.dir.createFile(test_out_file, .{});
485 defer file.close();
279 const file = try tmp.dir.createFile(io, test_out_file, .{});
280 defer file.close(io);
486281
487282 // Note: The test assumes createFile opens the file with CLOEXEC
488283 {
......@@ -522,18 +317,20 @@ test "sync" {
522317
523318test "fsync" {
524319 switch (native_os) {
525 .linux, .windows, .illumos => {},
320 .linux, .illumos => {},
526321 else => return error.SkipZigTest,
527322 }
528323
324 const io = testing.io;
325
529326 var tmp = tmpDir(.{});
530327 defer tmp.cleanup();
531328
532329 const test_out_file = "os_tmp_test";
533 const file = try tmp.dir.createFile(test_out_file, .{});
534 defer file.close();
330 const file = try tmp.dir.createFile(io, test_out_file, .{});
331 defer file.close(io);
535332
536 try posix.fsync(file.handle);
333 try file.sync(io);
537334 try posix.fdatasync(file.handle);
538335}
539336
......@@ -571,9 +368,9 @@ test "sigrtmin/max" {
571368 return error.SkipZigTest;
572369 }
573370
574 try std.testing.expect(posix.sigrtmin() >= 32);
575 try std.testing.expect(posix.sigrtmin() >= posix.system.sigrtmin());
576 try std.testing.expect(posix.sigrtmin() < posix.system.sigrtmax());
371 try expect(posix.sigrtmin() >= 32);
372 try expect(posix.sigrtmin() >= posix.system.sigrtmin());
373 try expect(posix.sigrtmin() < posix.system.sigrtmax());
577374}
578375
579376test "sigset empty/full" {
......@@ -646,27 +443,29 @@ test "dup & dup2" {
646443 else => return error.SkipZigTest,
647444 }
648445
446 const io = testing.io;
447
649448 var tmp = tmpDir(.{});
650449 defer tmp.cleanup();
651450
652451 {
653 var file = try tmp.dir.createFile("os_dup_test", .{});
654 defer file.close();
452 var file = try tmp.dir.createFile(io, "os_dup_test", .{});
453 defer file.close(io);
655454
656 var duped = std.fs.File{ .handle = try posix.dup(file.handle) };
657 defer duped.close();
658 try duped.writeAll("dup");
455 var duped = Io.File{ .handle = try posix.dup(file.handle) };
456 defer duped.close(io);
457 try duped.writeStreamingAll(io, "dup");
659458
660459 // Tests aren't run in parallel so using the next fd shouldn't be an issue.
661460 const new_fd = duped.handle + 1;
662461 try posix.dup2(file.handle, new_fd);
663 var dup2ed = std.fs.File{ .handle = new_fd };
664 defer dup2ed.close();
665 try dup2ed.writeAll("dup2");
462 var dup2ed = Io.File{ .handle = new_fd };
463 defer dup2ed.close(io);
464 try dup2ed.writeStreamingAll(io, "dup2");
666465 }
667466
668467 var buffer: [8]u8 = undefined;
669 try testing.expectEqualStrings("dupdup2", try tmp.dir.readFile("os_dup_test", &buffer));
468 try expectEqualStrings("dupdup2", try tmp.dir.readFile(io, "os_dup_test", &buffer));
670469}
671470
672471test "getpid" {
......@@ -684,208 +483,78 @@ test "getppid" {
684483 try expect(posix.getppid() >= 0);
685484}
686485
687test "writev longer than IOV_MAX" {
688 if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
689
690 var tmp = tmpDir(.{});
691 defer tmp.cleanup();
692
693 var file = try tmp.dir.createFile("pwritev", .{});
694 defer file.close();
695
696 const iovecs = [_]posix.iovec_const{.{ .base = "a", .len = 1 }} ** (posix.IOV_MAX + 1);
697 const amt = try file.writev(&iovecs);
698 try testing.expectEqual(@as(usize, posix.IOV_MAX), amt);
699}
700
701test "POSIX file locking with fcntl" {
702 if (native_os == .windows or native_os == .wasi) {
703 // Not POSIX.
704 return error.SkipZigTest;
705 }
706
707 if (true) {
708 // https://github.com/ziglang/zig/issues/11074
709 return error.SkipZigTest;
710 }
711
712 var tmp = tmpDir(.{});
713 defer tmp.cleanup();
714
715 // Create a temporary lock file
716 var file = try tmp.dir.createFile("lock", .{ .read = true });
717 defer file.close();
718 try file.setEndPos(2);
719 const fd = file.handle;
720
721 // Place an exclusive lock on the first byte, and a shared lock on the second byte:
722 var struct_flock = std.mem.zeroInit(posix.Flock, .{ .type = posix.F.WRLCK });
723 _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
724 struct_flock.start = 1;
725 struct_flock.type = posix.F.RDLCK;
726 _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
727
728 // Check the locks in a child process:
729 const pid = try posix.fork();
730 if (pid == 0) {
731 // child expects be denied the exclusive lock:
732 struct_flock.start = 0;
733 struct_flock.type = posix.F.WRLCK;
734 try expectError(error.Locked, posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock)));
735 // child expects to get the shared lock:
736 struct_flock.start = 1;
737 struct_flock.type = posix.F.RDLCK;
738 _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
739 // child waits for the exclusive lock in order to test deadlock:
740 struct_flock.start = 0;
741 struct_flock.type = posix.F.WRLCK;
742 _ = try posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock));
743 // child exits without continuing:
744 posix.exit(0);
745 } else {
746 // parent waits for child to get shared lock:
747 std.Thread.sleep(1 * std.time.ns_per_ms);
748 // parent expects deadlock when attempting to upgrade the shared lock to exclusive:
749 struct_flock.start = 1;
750 struct_flock.type = posix.F.WRLCK;
751 try expectError(error.DeadLock, posix.fcntl(fd, posix.F.SETLKW, @intFromPtr(&struct_flock)));
752 // parent releases exclusive lock:
753 struct_flock.start = 0;
754 struct_flock.type = posix.F.UNLCK;
755 _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
756 // parent releases shared lock:
757 struct_flock.start = 1;
758 struct_flock.type = posix.F.UNLCK;
759 _ = try posix.fcntl(fd, posix.F.SETLK, @intFromPtr(&struct_flock));
760 // parent waits for child:
761 const result = posix.waitpid(pid, 0);
762 try expect(result.status == 0 * 256);
763 }
764}
765
766486test "rename smoke test" {
767487 if (native_os == .wasi) return error.SkipZigTest;
768488 if (native_os == .windows) return error.SkipZigTest;
769489 if (native_os == .openbsd) return error.SkipZigTest;
770490
491 const io = testing.io;
492 const gpa = testing.allocator;
493
771494 var tmp = tmpDir(.{});
772495 defer tmp.cleanup();
773496
774 const base_path = try tmp.dir.realpathAlloc(a, ".");
775 defer a.free(base_path);
497 const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
498 defer gpa.free(base_path);
776499
777500 const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
778501
779502 {
780503 // Create some file using `open`.
781 const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
782 defer a.free(file_path);
504 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" });
505 defer gpa.free(file_path);
783506 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
784507 posix.close(fd);
785508
786509 // Rename the file
787 const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_file" });
788 defer a.free(new_file_path);
789 try posix.rename(file_path, new_file_path);
510 const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" });
511 defer gpa.free(new_file_path);
512 try Io.Dir.renameAbsolute(file_path, new_file_path, io);
790513 }
791514
792515 {
793516 // Try opening renamed file
794 const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" });
795 defer a.free(file_path);
517 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" });
518 defer gpa.free(file_path);
796519 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode);
797520 posix.close(fd);
798521 }
799522
800523 {
801524 // Try opening original file - should fail with error.FileNotFound
802 const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
803 defer a.free(file_path);
525 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" });
526 defer gpa.free(file_path);
804527 try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));
805528 }
806529
807530 {
808531 // Create some directory
809 const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
810 defer a.free(file_path);
532 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });
533 defer gpa.free(file_path);
811534 try posix.mkdir(file_path, mode);
812535
813536 // Rename the directory
814 const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" });
815 defer a.free(new_file_path);
816 try posix.rename(file_path, new_file_path);
537 const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });
538 defer gpa.free(new_file_path);
539 try Io.Dir.renameAbsolute(file_path, new_file_path, io);
817540 }
818541
819542 {
820543 // Try opening renamed directory
821 const file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" });
822 defer a.free(file_path);
544 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });
545 defer gpa.free(file_path);
823546 const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);
824547 posix.close(fd);
825548 }
826549
827550 {
828551 // Try opening original directory - should fail with error.FileNotFound
829 const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
830 defer a.free(file_path);
552 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });
553 defer gpa.free(file_path);
831554 try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode));
832555 }
833556}
834557
835test "access smoke test" {
836 if (native_os == .wasi) return error.SkipZigTest;
837 if (native_os == .windows) return error.SkipZigTest;
838 if (native_os == .openbsd) return error.SkipZigTest;
839
840 var tmp = tmpDir(.{});
841 defer tmp.cleanup();
842
843 const base_path = try tmp.dir.realpathAlloc(a, ".");
844 defer a.free(base_path);
845
846 const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
847 {
848 // Create some file using `open`.
849 const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
850 defer a.free(file_path);
851 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
852 posix.close(fd);
853 }
854
855 {
856 // Try to access() the file
857 const file_path = try fs.path.join(a, &.{ base_path, "some_file" });
858 defer a.free(file_path);
859 if (native_os == .windows) {
860 try posix.access(file_path, posix.F_OK);
861 } else {
862 try posix.access(file_path, posix.F_OK | posix.W_OK | posix.R_OK);
863 }
864 }
865
866 {
867 // Try to access() a non-existent file - should fail with error.FileNotFound
868 const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" });
869 defer a.free(file_path);
870 try expectError(error.FileNotFound, posix.access(file_path, posix.F_OK));
871 }
872
873 {
874 // Create some directory
875 const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
876 defer a.free(file_path);
877 try posix.mkdir(file_path, mode);
878 }
879
880 {
881 // Try to access() the directory
882 const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });
883 defer a.free(file_path);
884
885 try posix.access(file_path, posix.F_OK);
886 }
887}
888
889558test "timerfd" {
890559 if (native_os != .linux) return error.SkipZigTest;
891560
......@@ -903,142 +572,3 @@ test "timerfd" {
903572 const expect_disarmed_timer: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 0 } };
904573 try expectEqual(expect_disarmed_timer, git);
905574}
906
907test "isatty" {
908 var tmp = tmpDir(.{});
909 defer tmp.cleanup();
910
911 var file = try tmp.dir.createFile("foo", .{});
912 defer file.close();
913
914 try expectEqual(posix.isatty(file.handle), false);
915}
916
917test "pread with empty buffer" {
918 var tmp = tmpDir(.{});
919 defer tmp.cleanup();
920
921 var file = try tmp.dir.createFile("pread_empty", .{ .read = true });
922 defer file.close();
923
924 const bytes = try a.alloc(u8, 0);
925 defer a.free(bytes);
926
927 const rc = try posix.pread(file.handle, bytes, 0);
928 try expectEqual(rc, 0);
929}
930
931test "write with empty buffer" {
932 var tmp = tmpDir(.{});
933 defer tmp.cleanup();
934
935 var file = try tmp.dir.createFile("write_empty", .{});
936 defer file.close();
937
938 const bytes = try a.alloc(u8, 0);
939 defer a.free(bytes);
940
941 const rc = try posix.write(file.handle, bytes);
942 try expectEqual(rc, 0);
943}
944
945test "pwrite with empty buffer" {
946 var tmp = tmpDir(.{});
947 defer tmp.cleanup();
948
949 var file = try tmp.dir.createFile("pwrite_empty", .{});
950 defer file.close();
951
952 const bytes = try a.alloc(u8, 0);
953 defer a.free(bytes);
954
955 const rc = try posix.pwrite(file.handle, bytes, 0);
956 try expectEqual(rc, 0);
957}
958
959fn getFileMode(dir: posix.fd_t, path: []const u8) !posix.mode_t {
960 const path_z = try posix.toPosixPath(path);
961 const mode: posix.mode_t = if (native_os == .linux) blk: {
962 const stx = try linux.wrapped.statx(
963 dir,
964 &path_z,
965 posix.AT.SYMLINK_NOFOLLOW,
966 .{ .MODE = true },
967 );
968 std.debug.assert(stx.mask.MODE);
969 break :blk stx.mode;
970 } else blk: {
971 const st = try posix.fstatatZ(dir, &path_z, posix.AT.SYMLINK_NOFOLLOW);
972 break :blk st.mode;
973 };
974
975 return mode & 0b111_111_111;
976}
977
978fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
979 const actual = try getFileMode(dir, file);
980 try expectEqual(mode, actual & 0b111_111_111);
981}
982
983test "fchmodat smoke test" {
984 if (!std.fs.has_executable_bit) return error.SkipZigTest;
985
986 var tmp = tmpDir(.{});
987 defer tmp.cleanup();
988
989 try expectError(error.FileNotFound, posix.fchmodat(tmp.dir.fd, "regfile", 0o666, 0));
990 const fd = try posix.openat(
991 tmp.dir.fd,
992 "regfile",
993 .{ .ACCMODE = .WRONLY, .CREAT = true, .EXCL = true, .TRUNC = true },
994 0o644,
995 );
996 posix.close(fd);
997
998 try posix.symlinkat("regfile", tmp.dir.fd, "symlink");
999 const sym_mode = try getFileMode(tmp.dir.fd, "symlink");
1000
1001 try posix.fchmodat(tmp.dir.fd, "regfile", 0o640, 0);
1002 try expectMode(tmp.dir.fd, "regfile", 0o640);
1003 try posix.fchmodat(tmp.dir.fd, "regfile", 0o600, posix.AT.SYMLINK_NOFOLLOW);
1004 try expectMode(tmp.dir.fd, "regfile", 0o600);
1005
1006 try posix.fchmodat(tmp.dir.fd, "symlink", 0o640, 0);
1007 try expectMode(tmp.dir.fd, "regfile", 0o640);
1008 try expectMode(tmp.dir.fd, "symlink", sym_mode);
1009
1010 var test_link = true;
1011 posix.fchmodat(tmp.dir.fd, "symlink", 0o600, posix.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) {
1012 error.OperationNotSupported => test_link = false,
1013 else => |e| return e,
1014 };
1015 if (test_link)
1016 try expectMode(tmp.dir.fd, "symlink", 0o600);
1017 try expectMode(tmp.dir.fd, "regfile", 0o640);
1018}
1019
1020const CommonOpenFlags = packed struct {
1021 ACCMODE: posix.ACCMODE = .RDONLY,
1022 CREAT: bool = false,
1023 EXCL: bool = false,
1024 LARGEFILE: bool = false,
1025 DIRECTORY: bool = false,
1026 CLOEXEC: bool = false,
1027 NONBLOCK: bool = false,
1028
1029 pub fn lower(cof: CommonOpenFlags) posix.O {
1030 var result: posix.O = if (native_os == .wasi) .{
1031 .read = cof.ACCMODE != .WRONLY,
1032 .write = cof.ACCMODE != .RDONLY,
1033 } else .{
1034 .ACCMODE = cof.ACCMODE,
1035 };
1036 result.CREAT = cof.CREAT;
1037 result.EXCL = cof.EXCL;
1038 result.DIRECTORY = cof.DIRECTORY;
1039 result.NONBLOCK = cof.NONBLOCK;
1040 if (@hasField(posix.O, "CLOEXEC")) result.CLOEXEC = cof.CLOEXEC;
1041 if (@hasField(posix.O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE;
1042 return result;
1043 }
1044};
lib/std/process.zig+245-26
......@@ -1,24 +1,33 @@
1const std = @import("std.zig");
21const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3
4const std = @import("std.zig");
5const Io = std.Io;
6const File = std.Io.File;
37const fs = std.fs;
48const mem = std.mem;
59const math = std.math;
6const Allocator = mem.Allocator;
10const Allocator = std.mem.Allocator;
711const assert = std.debug.assert;
812const testing = std.testing;
9const native_os = builtin.os.tag;
1013const posix = std.posix;
1114const windows = std.os.windows;
1215const unicode = std.unicode;
16const max_path_bytes = std.fs.max_path_bytes;
1317
1418pub const Child = @import("process/Child.zig");
15pub const abort = posix.abort;
16pub const exit = posix.exit;
1719pub const changeCurDir = posix.chdir;
1820pub const changeCurDirZ = posix.chdirZ;
1921
2022pub const GetCwdError = posix.GetCwdError;
2123
24/// This is the global, process-wide protection to coordinate stderr writes.
25///
26/// The primary motivation for recursive mutex here is so that a panic while
27/// stderr mutex is held still dumps the stack trace and other debug
28/// information.
29pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;
30
2231/// The result is a slice of `out_buffer`, from index `0`.
2332/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
2433/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
......@@ -35,7 +44,7 @@ pub const GetCwdAllocError = Allocator.Error || error{CurrentWorkingDirectoryUnl
3544pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 {
3645 // The use of max_path_bytes here is just a heuristic: most paths will fit
3746 // in stack_buf, avoiding an extra allocation in the common case.
38 var stack_buf: [fs.max_path_bytes]u8 = undefined;
47 var stack_buf: [max_path_bytes]u8 = undefined;
3948 var heap_buf: ?[]u8 = null;
4049 defer if (heap_buf) |buf| allocator.free(buf);
4150
......@@ -437,25 +446,25 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
437446}
438447
439448/// On Windows, `key` must be valid WTF-8.
440pub fn hasEnvVarConstant(comptime key: []const u8) bool {
449pub inline fn hasEnvVarConstant(comptime key: []const u8) bool {
441450 if (native_os == .windows) {
442451 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
443452 return getenvW(key_w) != null;
444453 } else if (native_os == .wasi and !builtin.link_libc) {
445 @compileError("hasEnvVarConstant is not supported for WASI without libc");
454 return false;
446455 } else {
447456 return posix.getenv(key) != null;
448457 }
449458}
450459
451460/// On Windows, `key` must be valid WTF-8.
452pub fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
461pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
453462 if (native_os == .windows) {
454463 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
455464 const value = getenvW(key_w) orelse return false;
456465 return value.len != 0;
457466 } else if (native_os == .wasi and !builtin.link_libc) {
458 @compileError("hasNonEmptyEnvVarConstant is not supported for WASI without libc");
467 return false;
459468 } else {
460469 const value = posix.getenv(key) orelse return false;
461470 return value.len != 0;
......@@ -1571,9 +1580,9 @@ pub fn getUserInfo(name: []const u8) !UserInfo {
15711580
15721581/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
15731582/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
1574pub fn posixGetUserInfo(name: []const u8) !UserInfo {
1575 const file = try std.fs.openFileAbsolute("/etc/passwd", .{});
1576 defer file.close();
1583pub fn posixGetUserInfo(io: Io, name: []const u8) !UserInfo {
1584 const file = try Io.Dir.openFileAbsolute(io, "/etc/passwd", .{});
1585 defer file.close(io);
15771586 var buffer: [4096]u8 = undefined;
15781587 var file_reader = file.reader(&buffer);
15791588 return posixGetUserInfoPasswdStream(name, &file_reader.interface) catch |err| switch (err) {
......@@ -1839,21 +1848,19 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 {
18391848 }
18401849}
18411850
1842/// Indicate that we are now terminating with a successful exit code.
1843/// In debug builds, this is a no-op, so that the calling code's
1844/// cleanup mechanisms are tested and so that external tools that
1845/// check for resource leaks can be accurate. In release builds, this
1846/// calls exit(0), and does not return.
1847pub fn cleanExit() void {
1848 if (builtin.mode == .Debug) {
1849 return;
1850 } else {
1851 std.debug.lockStdErr();
1852 exit(0);
1853 }
1851/// Indicate intent to terminate with a successful exit code.
1852///
1853/// In debug builds, this is a no-op, so that the calling code's cleanup
1854/// mechanisms are tested and so that external tools checking for resource
1855/// leaks can be accurate. In release builds, this calls `exit` with code zero,
1856/// and does not return.
1857pub fn cleanExit(io: Io) void {
1858 if (builtin.mode == .Debug) return;
1859 _ = io.lockStderr(&.{}, .no_color) catch {};
1860 exit(0);
18541861}
18551862
1856/// Raise the open file descriptor limit.
1863/// Request ability to have more open file descriptors simultaneously.
18571864///
18581865/// On some systems, this raises the limit before seeing ProcessFdQuotaExceeded
18591866/// errors. On other systems, this does nothing.
......@@ -2110,3 +2117,215 @@ pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {
21102117 std.log.err(format, format_arguments);
21112118 exit(1);
21122119}
2120
2121pub const ExecutablePathBaseError = error{
2122 FileNotFound,
2123 AccessDenied,
2124 /// The operating system does not support an executable learning its own
2125 /// path.
2126 OperationUnsupported,
2127 NotDir,
2128 SymLinkLoop,
2129 InputOutput,
2130 FileTooBig,
2131 IsDir,
2132 ProcessFdQuotaExceeded,
2133 SystemFdQuotaExceeded,
2134 NoDevice,
2135 SystemResources,
2136 NoSpaceLeft,
2137 FileSystem,
2138 BadPathName,
2139 DeviceBusy,
2140 SharingViolation,
2141 PipeBusy,
2142 NotLink,
2143 PathAlreadyExists,
2144 /// On Windows, `\\server` or `\\server\share` was not found.
2145 NetworkNotFound,
2146 ProcessNotFound,
2147 /// On Windows, antivirus software is enabled by default. It can be
2148 /// disabled, but Windows Update sometimes ignores the user's preference
2149 /// and re-enables it. When enabled, antivirus software on Windows
2150 /// intercepts file system operations and makes them significantly slower
2151 /// in addition to possibly failing with this error code.
2152 AntivirusInterference,
2153 /// On Windows, the volume does not contain a recognized file system. File
2154 /// system drivers might not be loaded, or the volume may be corrupt.
2155 UnrecognizedVolume,
2156 PermissionDenied,
2157} || Io.Cancelable || Io.UnexpectedError;
2158
2159pub const ExecutablePathAllocError = ExecutablePathBaseError || Allocator.Error;
2160
2161pub fn executablePathAlloc(io: Io, allocator: Allocator) ExecutablePathAllocError![:0]u8 {
2162 var buffer: [max_path_bytes]u8 = undefined;
2163 const n = executablePath(io, &buffer) catch |err| switch (err) {
2164 error.NameTooLong => unreachable,
2165 else => |e| return e,
2166 };
2167 return allocator.dupeZ(u8, buffer[0..n]);
2168}
2169
2170pub const ExecutablePathError = ExecutablePathBaseError || error{NameTooLong};
2171
2172/// Get the path to the current executable, following symlinks.
2173///
2174/// This function may return an error if the current executable
2175/// was deleted after spawning.
2176///
2177/// Returned value is a slice of out_buffer.
2178///
2179/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
2180/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
2181///
2182/// On Linux, depends on procfs being mounted. If the currently executing binary has
2183/// been deleted, the file path looks something like "/a/b/c/exe (deleted)".
2184///
2185/// See also:
2186/// * `executableDirPath` - to obtain only the directory
2187/// * `openExecutable` - to obtain only an open file handle
2188pub fn executablePath(io: Io, out_buffer: []u8) ExecutablePathError!usize {
2189 return io.vtable.processExecutablePath(io.userdata, out_buffer);
2190}
2191
2192/// Get the directory path that contains the current executable.
2193///
2194/// Returns index into `out_buffer`.
2195///
2196/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
2197/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
2198pub fn executableDirPath(io: Io, out_buffer: []u8) ExecutablePathError!usize {
2199 const n = try executablePath(io, out_buffer);
2200 // Assert that the OS APIs return absolute paths, and therefore dirname
2201 // will not return null.
2202 return std.fs.path.dirname(out_buffer[0..n]).?.len;
2203}
2204
2205/// Same as `executableDirPath` except allocates the result.
2206pub fn executableDirPathAlloc(io: Io, allocator: Allocator) ExecutablePathAllocError![]u8 {
2207 var buffer: [max_path_bytes]u8 = undefined;
2208 const dir_path_len = executableDirPath(io, &buffer) catch |err| switch (err) {
2209 error.NameTooLong => unreachable,
2210 else => |e| return e,
2211 };
2212 return allocator.dupe(u8, buffer[0..dir_path_len]);
2213}
2214
2215pub const OpenExecutableError = File.OpenError || ExecutablePathError || File.LockError;
2216
2217pub fn openExecutable(io: Io, flags: File.OpenFlags) OpenExecutableError!File {
2218 return io.vtable.processExecutableOpen(io.userdata, flags);
2219}
2220
2221/// Causes abnormal process termination.
2222///
2223/// If linking against libc, this calls `std.c.abort`. Otherwise it raises
2224/// SIGABRT followed by SIGKILL.
2225///
2226/// Invokes the current signal handler for SIGABRT, if any.
2227pub fn abort() noreturn {
2228 @branchHint(.cold);
2229 // MSVCRT abort() sometimes opens a popup window which is undesirable, so
2230 // even when linking libc on Windows we use our own abort implementation.
2231 // See https://github.com/ziglang/zig/issues/2071 for more details.
2232 if (native_os == .windows) {
2233 if (builtin.mode == .Debug and windows.peb().BeingDebugged != 0) {
2234 @breakpoint();
2235 }
2236 windows.ntdll.RtlExitUserProcess(3);
2237 }
2238 if (!builtin.link_libc and native_os == .linux) {
2239 // The Linux man page says that the libc abort() function
2240 // "first unblocks the SIGABRT signal", but this is a footgun
2241 // for user-defined signal handlers that want to restore some state in
2242 // some program sections and crash in others.
2243 // So, the user-installed SIGABRT handler is run, if present.
2244 posix.raise(.ABRT) catch {};
2245
2246 // Disable all signal handlers.
2247 const filledset = std.os.linux.sigfillset();
2248 posix.sigprocmask(posix.SIG.BLOCK, &filledset, null);
2249
2250 // Only one thread may proceed to the rest of abort().
2251 if (!builtin.single_threaded) {
2252 const global = struct {
2253 var abort_entered: bool = false;
2254 };
2255 while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .seq_cst, .seq_cst)) |_| {}
2256 }
2257
2258 // Install default handler so that the tkill below will terminate.
2259 const sigact: posix.Sigaction = .{
2260 .handler = .{ .handler = posix.SIG.DFL },
2261 .mask = posix.sigemptyset(),
2262 .flags = 0,
2263 };
2264 posix.sigaction(.ABRT, &sigact, null);
2265
2266 _ = std.os.linux.tkill(std.os.linux.gettid(), .ABRT);
2267
2268 var sigabrtmask = posix.sigemptyset();
2269 posix.sigaddset(&sigabrtmask, .ABRT);
2270 posix.sigprocmask(posix.SIG.UNBLOCK, &sigabrtmask, null);
2271
2272 // Beyond this point should be unreachable.
2273 @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0;
2274 posix.raise(.KILL) catch {};
2275 exit(127); // Pid 1 might not be signalled in some containers.
2276 }
2277 switch (native_os) {
2278 .uefi, .wasi, .emscripten, .cuda, .amdhsa => @trap(),
2279 else => posix.system.abort(),
2280 }
2281}
2282
2283/// Exits all threads of the program with the specified status code.
2284pub fn exit(status: u8) noreturn {
2285 if (builtin.link_libc) {
2286 std.c.exit(status);
2287 } else switch (native_os) {
2288 .windows => windows.ntdll.RtlExitUserProcess(status),
2289 .wasi => std.os.wasi.proc_exit(status),
2290 .linux => {
2291 if (!builtin.single_threaded) std.os.linux.exit_group(status);
2292 posix.system.exit(status);
2293 },
2294 .uefi => {
2295 const uefi = std.os.uefi;
2296 // exit() is only available if exitBootServices() has not been called yet.
2297 // This call to exit should not fail, so we catch-ignore errors.
2298 if (uefi.system_table.boot_services) |bs| {
2299 bs.exit(uefi.handle, @enumFromInt(status), null) catch {};
2300 }
2301 // If we can't exit, reboot the system instead.
2302 uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), null);
2303 },
2304 else => posix.system.exit(status),
2305 }
2306}
2307
2308pub const SetCurrentDirError = error{
2309 AccessDenied,
2310 BadPathName,
2311 FileNotFound,
2312 FileSystem,
2313 NameTooLong,
2314 NoDevice,
2315 NotDir,
2316 OperationUnsupported,
2317 UnrecognizedVolume,
2318} || Io.Cancelable || Io.UnexpectedError;
2319
2320/// Changes the current working directory to the open directory handle.
2321/// Corresponds to "fchdir" in libc.
2322///
2323/// This modifies global process state and can have surprising effects in
2324/// multithreaded applications. Most applications and especially libraries
2325/// should not call this function as a general rule, however it can have use
2326/// cases in, for example, implementing a shell, or child process execution.
2327///
2328/// Calling this function makes code less portable and less reusable.
2329pub fn setCurrentDir(io: Io, dir: Io.Dir) !void {
2330 return io.vtable.processSetCurrentDir(io.userdata, dir);
2331}
lib/std/process/Child.zig+81-82
......@@ -1,13 +1,14 @@
1const ChildProcess = @This();
1const Child = @This();
22
33const builtin = @import("builtin");
44const native_os = builtin.os.tag;
55
66const std = @import("../std.zig");
7const Io = std.Io;
78const unicode = std.unicode;
89const fs = std.fs;
910const process = std.process;
10const File = std.fs.File;
11const File = std.Io.File;
1112const windows = std.os.windows;
1213const linux = std.os.linux;
1314const posix = std.posix;
......@@ -30,7 +31,7 @@ pub const Id = switch (native_os) {
3031id: Id,
3132thread_handle: if (native_os == .windows) windows.HANDLE else void,
3233
33allocator: mem.Allocator,
34allocator: Allocator,
3435
3536/// The writing end of the child process's standard input pipe.
3637/// Usage requires `stdin_behavior == StdIo.Pipe`.
......@@ -76,7 +77,7 @@ cwd: ?[]const u8,
7677/// Set to change the current working directory when spawning the child process.
7778/// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
7879/// Once that is done, `cwd` will be deprecated in favor of this field.
79cwd_dir: ?fs.Dir = null,
80cwd_dir: ?Io.Dir = null,
8081
8182err_pipe: if (native_os == .windows) void else ?posix.fd_t,
8283
......@@ -228,7 +229,7 @@ pub const StdIo = enum {
228229};
229230
230231/// First argument in argv is the executable.
231pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
232pub fn init(argv: []const []const u8, allocator: Allocator) Child {
232233 return .{
233234 .allocator = allocator,
234235 .argv = argv,
......@@ -251,7 +252,7 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
251252 };
252253}
253254
254pub fn setUserName(self: *ChildProcess, name: []const u8) !void {
255pub fn setUserName(self: *Child, name: []const u8) !void {
255256 const user_info = try process.getUserInfo(name);
256257 self.uid = user_info.uid;
257258 self.gid = user_info.gid;
......@@ -259,35 +260,35 @@ pub fn setUserName(self: *ChildProcess, name: []const u8) !void {
259260
260261/// On success must call `kill` or `wait`.
261262/// After spawning the `id` is available.
262pub fn spawn(self: *ChildProcess) SpawnError!void {
263pub fn spawn(self: *Child, io: Io) SpawnError!void {
263264 if (!process.can_spawn) {
264265 @compileError("the target operating system cannot spawn processes");
265266 }
266267
267268 if (native_os == .windows) {
268 return self.spawnWindows();
269 return self.spawnWindows(io);
269270 } else {
270 return self.spawnPosix();
271 return self.spawnPosix(io);
271272 }
272273}
273274
274pub fn spawnAndWait(self: *ChildProcess) SpawnError!Term {
275 try self.spawn();
276 return self.wait();
275pub fn spawnAndWait(child: *Child, io: Io) SpawnError!Term {
276 try child.spawn(io);
277 return child.wait(io);
277278}
278279
279280/// Forcibly terminates child process and then cleans up all resources.
280pub fn kill(self: *ChildProcess) !Term {
281pub fn kill(self: *Child, io: Io) !Term {
281282 if (native_os == .windows) {
282 return self.killWindows(1);
283 return self.killWindows(io, 1);
283284 } else {
284 return self.killPosix();
285 return self.killPosix(io);
285286 }
286287}
287288
288pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term {
289pub fn killWindows(self: *Child, io: Io, exit_code: windows.UINT) !Term {
289290 if (self.term) |term| {
290 self.cleanupStreams();
291 self.cleanupStreams(io);
291292 return term;
292293 }
293294
......@@ -303,20 +304,20 @@ pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term {
303304 },
304305 else => return err,
305306 };
306 try self.waitUnwrappedWindows();
307 try self.waitUnwrappedWindows(io);
307308 return self.term.?;
308309}
309310
310pub fn killPosix(self: *ChildProcess) !Term {
311pub fn killPosix(self: *Child, io: Io) !Term {
311312 if (self.term) |term| {
312 self.cleanupStreams();
313 self.cleanupStreams(io);
313314 return term;
314315 }
315316 posix.kill(self.id, posix.SIG.TERM) catch |err| switch (err) {
316317 error.ProcessNotFound => return error.AlreadyTerminated,
317318 else => return err,
318319 };
319 self.waitUnwrappedPosix();
320 self.waitUnwrappedPosix(io);
320321 return self.term.?;
321322}
322323
......@@ -324,7 +325,7 @@ pub const WaitError = SpawnError || std.os.windows.GetProcessMemoryInfoError;
324325
325326/// On some targets, `spawn` may not report all spawn errors, such as `error.InvalidExe`.
326327/// This function will block until any spawn errors can be reported, and return them.
327pub fn waitForSpawn(self: *ChildProcess) SpawnError!void {
328pub fn waitForSpawn(self: *Child) SpawnError!void {
328329 if (native_os == .windows) return; // `spawn` reports everything
329330 if (self.term) |term| {
330331 _ = term catch |spawn_err| return spawn_err;
......@@ -354,15 +355,15 @@ pub fn waitForSpawn(self: *ChildProcess) SpawnError!void {
354355}
355356
356357/// Blocks until child process terminates and then cleans up all resources.
357pub fn wait(self: *ChildProcess) WaitError!Term {
358pub fn wait(self: *Child, io: Io) WaitError!Term {
358359 try self.waitForSpawn(); // report spawn errors
359360 if (self.term) |term| {
360 self.cleanupStreams();
361 self.cleanupStreams(io);
361362 return term;
362363 }
363364 switch (native_os) {
364 .windows => try self.waitUnwrappedWindows(),
365 else => self.waitUnwrappedPosix(),
365 .windows => try self.waitUnwrappedWindows(io),
366 else => self.waitUnwrappedPosix(io),
366367 }
367368 self.id = undefined;
368369 return self.term.?;
......@@ -380,7 +381,7 @@ pub const RunResult = struct {
380381///
381382/// The process must be started with stdout_behavior and stderr_behavior == .Pipe
382383pub fn collectOutput(
383 child: ChildProcess,
384 child: Child,
384385 /// Used for `stdout` and `stderr`.
385386 allocator: Allocator,
386387 stdout: *ArrayList(u8),
......@@ -434,11 +435,10 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix
434435
435436/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
436437/// If it succeeds, the caller owns result.stdout and result.stderr memory.
437pub fn run(args: struct {
438 allocator: mem.Allocator,
438pub fn run(allocator: Allocator, io: Io, args: struct {
439439 argv: []const []const u8,
440440 cwd: ?[]const u8 = null,
441 cwd_dir: ?fs.Dir = null,
441 cwd_dir: ?Io.Dir = null,
442442 /// Required if unable to access the current env map (e.g. building a
443443 /// library on some platforms).
444444 env_map: ?*const EnvMap = null,
......@@ -446,7 +446,7 @@ pub fn run(args: struct {
446446 expand_arg0: Arg0Expand = .no_expand,
447447 progress_node: std.Progress.Node = std.Progress.Node.none,
448448}) RunError!RunResult {
449 var child = ChildProcess.init(args.argv, args.allocator);
449 var child = Child.init(args.argv, allocator);
450450 child.stdin_behavior = .Ignore;
451451 child.stdout_behavior = .Pipe;
452452 child.stderr_behavior = .Pipe;
......@@ -457,24 +457,24 @@ pub fn run(args: struct {
457457 child.progress_node = args.progress_node;
458458
459459 var stdout: ArrayList(u8) = .empty;
460 defer stdout.deinit(args.allocator);
460 defer stdout.deinit(allocator);
461461 var stderr: ArrayList(u8) = .empty;
462 defer stderr.deinit(args.allocator);
462 defer stderr.deinit(allocator);
463463
464 try child.spawn();
464 try child.spawn(io);
465465 errdefer {
466 _ = child.kill() catch {};
466 _ = child.kill(io) catch {};
467467 }
468 try child.collectOutput(args.allocator, &stdout, &stderr, args.max_output_bytes);
468 try child.collectOutput(allocator, &stdout, &stderr, args.max_output_bytes);
469469
470470 return .{
471 .stdout = try stdout.toOwnedSlice(args.allocator),
472 .stderr = try stderr.toOwnedSlice(args.allocator),
473 .term = try child.wait(),
471 .stdout = try stdout.toOwnedSlice(allocator),
472 .stderr = try stderr.toOwnedSlice(allocator),
473 .term = try child.wait(io),
474474 };
475475}
476476
477fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void {
477fn waitUnwrappedWindows(self: *Child, io: Io) WaitError!void {
478478 const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false);
479479
480480 self.term = @as(SpawnError!Term, x: {
......@@ -492,11 +492,11 @@ fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void {
492492
493493 posix.close(self.id);
494494 posix.close(self.thread_handle);
495 self.cleanupStreams();
495 self.cleanupStreams(io);
496496 return result;
497497}
498498
499fn waitUnwrappedPosix(self: *ChildProcess) void {
499fn waitUnwrappedPosix(self: *Child, io: Io) void {
500500 const res: posix.WaitPidResult = res: {
501501 if (self.request_resource_usage_statistics) {
502502 switch (native_os) {
......@@ -527,25 +527,25 @@ fn waitUnwrappedPosix(self: *ChildProcess) void {
527527 break :res posix.waitpid(self.id, 0);
528528 };
529529 const status = res.status;
530 self.cleanupStreams();
530 self.cleanupStreams(io);
531531 self.handleWaitResult(status);
532532}
533533
534fn handleWaitResult(self: *ChildProcess, status: u32) void {
534fn handleWaitResult(self: *Child, status: u32) void {
535535 self.term = statusToTerm(status);
536536}
537537
538fn cleanupStreams(self: *ChildProcess) void {
538fn cleanupStreams(self: *Child, io: Io) void {
539539 if (self.stdin) |*stdin| {
540 stdin.close();
540 stdin.close(io);
541541 self.stdin = null;
542542 }
543543 if (self.stdout) |*stdout| {
544 stdout.close();
544 stdout.close(io);
545545 self.stdout = null;
546546 }
547547 if (self.stderr) |*stderr| {
548 stderr.close();
548 stderr.close(io);
549549 self.stderr = null;
550550 }
551551}
......@@ -561,7 +561,7 @@ fn statusToTerm(status: u32) Term {
561561 Term{ .Unknown = status };
562562}
563563
564fn spawnPosix(self: *ChildProcess) SpawnError!void {
564fn spawnPosix(self: *Child, io: Io) SpawnError!void {
565565 // The child process does need to access (one end of) these pipes. However,
566566 // we must initially set CLOEXEC to avoid a race condition. If another thread
567567 // is racing to spawn a different child process, we don't want it to inherit
......@@ -596,7 +596,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
596596 error.NoSpaceLeft => unreachable,
597597 error.FileTooBig => unreachable,
598598 error.DeviceBusy => unreachable,
599 error.FileLocksNotSupported => unreachable,
599 error.FileLocksUnsupported => unreachable,
600600 error.BadPathName => unreachable, // Windows-only
601601 error.WouldBlock => unreachable,
602602 error.NetworkNotFound => unreachable, // Windows-only
......@@ -659,7 +659,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
659659 })).ptr;
660660 } else {
661661 // TODO come up with a solution for this.
662 @panic("missing std lib enhancement: ChildProcess implementation has no way to collect the environment variables to forward to the child process");
662 @panic("missing std lib enhancement: std.process.Child implementation has no way to collect the environment variables to forward to the child process");
663663 }
664664 };
665665
......@@ -671,41 +671,41 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
671671 const pid_result = try posix.fork();
672672 if (pid_result == 0) {
673673 // we are the child
674 setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
675 setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
676 setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
674 setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err);
675 setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err);
676 setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err);
677677
678678 if (self.cwd_dir) |cwd| {
679 posix.fchdir(cwd.fd) catch |err| forkChildErrReport(err_pipe[1], err);
679 posix.fchdir(cwd.handle) catch |err| forkChildErrReport(io, err_pipe[1], err);
680680 } else if (self.cwd) |cwd| {
681 posix.chdir(cwd) catch |err| forkChildErrReport(err_pipe[1], err);
681 posix.chdir(cwd) catch |err| forkChildErrReport(io, err_pipe[1], err);
682682 }
683683
684684 // Must happen after fchdir above, the cwd file descriptor might be
685685 // equal to prog_fileno and be clobbered by this dup2 call.
686 if (prog_pipe[1] != -1) posix.dup2(prog_pipe[1], prog_fileno) catch |err| forkChildErrReport(err_pipe[1], err);
686 if (prog_pipe[1] != -1) posix.dup2(prog_pipe[1], prog_fileno) catch |err| forkChildErrReport(io, err_pipe[1], err);
687687
688688 if (self.gid) |gid| {
689 posix.setregid(gid, gid) catch |err| forkChildErrReport(err_pipe[1], err);
689 posix.setregid(gid, gid) catch |err| forkChildErrReport(io, err_pipe[1], err);
690690 }
691691
692692 if (self.uid) |uid| {
693 posix.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err);
693 posix.setreuid(uid, uid) catch |err| forkChildErrReport(io, err_pipe[1], err);
694694 }
695695
696696 if (self.pgid) |pid| {
697 posix.setpgid(0, pid) catch |err| forkChildErrReport(err_pipe[1], err);
697 posix.setpgid(0, pid) catch |err| forkChildErrReport(io, err_pipe[1], err);
698698 }
699699
700700 if (self.start_suspended) {
701 posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(err_pipe[1], err);
701 posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(io, err_pipe[1], err);
702702 }
703703
704704 const err = switch (self.expand_arg0) {
705705 .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),
706706 .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),
707707 };
708 forkChildErrReport(err_pipe[1], err);
708 forkChildErrReport(io, err_pipe[1], err);
709709 }
710710
711711 // we are the parent
......@@ -750,7 +750,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
750750 self.progress_node.setIpcFd(prog_pipe[0]);
751751}
752752
753fn spawnWindows(self: *ChildProcess) SpawnError!void {
753fn spawnWindows(self: *Child, io: Io) SpawnError!void {
754754 var saAttr = windows.SECURITY_ATTRIBUTES{
755755 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
756756 .bInheritHandle = windows.TRUE,
......@@ -880,7 +880,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
880880 const app_name_wtf8 = self.argv[0];
881881 const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);
882882
883 // the cwd set in ChildProcess is in effect when choosing the executable path
883 // the cwd set in Child is in effect when choosing the executable path
884884 // to match posix semantics
885885 var cwd_path_w_needs_free = false;
886886 const cwd_path_w = x: {
......@@ -953,7 +953,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
953953 try dir_buf.appendSlice(self.allocator, app_dir);
954954 }
955955
956 windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {
956 windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {
957957 const original_err = switch (no_path_err) {
958958 // argv[0] contains unsupported characters that will never resolve to a valid exe.
959959 error.InvalidArg0 => return error.FileNotFound,
......@@ -965,7 +965,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
965965 // If the app name had path separators, that disallows PATH searching,
966966 // and there's no need to search the PATH if the app name is absolute.
967967 // We still search the path if the cwd is absolute because of the
968 // "cwd set in ChildProcess is in effect when choosing the executable path
968 // "cwd set in Child is in effect when choosing the executable path
969969 // to match posix semantics" behavior--we don't want to skip searching
970970 // the PATH just because we were trying to set the cwd of the child process.
971971 if (app_dirname_w != null or app_name_is_absolute) {
......@@ -977,7 +977,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
977977 dir_buf.clearRetainingCapacity();
978978 try dir_buf.appendSlice(self.allocator, search_path);
979979
980 if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {
980 if (windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {
981981 break :run;
982982 } else |err| switch (err) {
983983 // argv[0] contains unsupported characters that will never resolve to a valid exe.
......@@ -1039,8 +1039,8 @@ fn destroyPipe(pipe: [2]posix.fd_t) void {
10391039
10401040// Child of fork calls this to report an error to the fork parent.
10411041// Then the child exits.
1042fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
1043 writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {};
1042fn forkChildErrReport(io: Io, fd: i32, err: Child.SpawnError) noreturn {
1043 writeIntFd(io, fd, @as(ErrInt, @intFromError(err))) catch {};
10441044 // If we're linking libc, some naughty applications may have registered atexit handlers
10451045 // which we really do not want to run in the fork child. I caught LLVM doing this and
10461046 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,
......@@ -1049,12 +1049,12 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
10491049 // The _exit(2) function does nothing but make the exit syscall, unlike exit(3)
10501050 std.c._exit(1);
10511051 }
1052 posix.exit(1);
1052 posix.system.exit(1);
10531053}
10541054
1055fn writeIntFd(fd: i32, value: ErrInt) !void {
1055fn writeIntFd(io: Io, fd: i32, value: ErrInt) !void {
10561056 var buffer: [8]u8 = undefined;
1057 var fw: std.fs.File.Writer = .initStreaming(.{ .handle = fd }, &buffer);
1057 var fw: File.Writer = .initStreaming(.{ .handle = fd }, io, &buffer);
10581058 fw.interface.writeInt(u64, value, .little) catch unreachable;
10591059 fw.interface.flush() catch return error.SystemResources;
10601060}
......@@ -1078,7 +1078,8 @@ const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
10781078/// Note: `app_buf` should not contain any leading path separators.
10791079/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
10801080fn windowsCreateProcessPathExt(
1081 allocator: mem.Allocator,
1081 allocator: Allocator,
1082 io: Io,
10821083 dir_buf: *ArrayList(u16),
10831084 app_buf: *ArrayList(u16),
10841085 pathext: [:0]const u16,
......@@ -1122,16 +1123,14 @@ fn windowsCreateProcessPathExt(
11221123 // Under those conditions, here we will have access to lower level directory
11231124 // opening function knowing which implementation we are in. Here, we imitate
11241125 // that scenario.
1125 var threaded: std.Io.Threaded = .init_single_threaded;
1126 const io = threaded.ioBasic();
1127
11281126 var dir = dir: {
11291127 // needs to be null-terminated
11301128 try dir_buf.append(allocator, 0);
11311129 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
11321130 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
11331131 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
1134 break :dir threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
1132 // TODO eliminate this reference
1133 break :dir Io.Threaded.global_single_threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
11351134 .iterate = true,
11361135 }) catch return error.FileNotFound;
11371136 };
......@@ -1525,9 +1524,9 @@ const WindowsCommandLineCache = struct {
15251524 script_cmd_line: ?[:0]u16 = null,
15261525 cmd_exe_path: ?[:0]u16 = null,
15271526 argv: []const []const u8,
1528 allocator: mem.Allocator,
1527 allocator: Allocator,
15291528
1530 fn init(allocator: mem.Allocator, argv: []const []const u8) WindowsCommandLineCache {
1529 fn init(allocator: Allocator, argv: []const []const u8) WindowsCommandLineCache {
15311530 return .{
15321531 .allocator = allocator,
15331532 .argv = argv,
......@@ -1571,7 +1570,7 @@ const WindowsCommandLineCache = struct {
15711570
15721571/// Returns the absolute path of `cmd.exe` within the Windows system directory.
15731572/// The caller owns the returned slice.
1574fn windowsCmdExePath(allocator: mem.Allocator) error{ OutOfMemory, Unexpected }![:0]u16 {
1573fn windowsCmdExePath(allocator: Allocator) error{ OutOfMemory, Unexpected }![:0]u16 {
15751574 var buf = try ArrayList(u16).initCapacity(allocator, 128);
15761575 errdefer buf.deinit(allocator);
15771576 while (true) {
......@@ -1608,7 +1607,7 @@ const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
16081607///
16091608/// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead.
16101609fn argvToCommandLineWindows(
1611 allocator: mem.Allocator,
1610 allocator: Allocator,
16121611 argv: []const []const u8,
16131612) ArgvToCommandLineError![:0]u16 {
16141613 var buf = std.array_list.Managed(u8).init(allocator);
......@@ -1784,7 +1783,7 @@ const ArgvToScriptCommandLineError = error{
17841783/// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise.
17851784/// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem.
17861785fn argvToScriptCommandLineWindows(
1787 allocator: mem.Allocator,
1786 allocator: Allocator,
17881787 /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD.
17891788 /// The script must have been verified to exist at this path before calling this function.
17901789 script_path: []const u16,
lib/std/start.zig+13-3
......@@ -110,7 +110,7 @@ fn main2() callconv(.c) c_int {
110110}
111111
112112fn _start2() callconv(.withStackAlign(.c, 1)) noreturn {
113 std.posix.exit(callMain());
113 std.process.exit(callMain());
114114}
115115
116116fn spirvMain2() callconv(.kernel) void {
......@@ -118,7 +118,7 @@ fn spirvMain2() callconv(.kernel) void {
118118}
119119
120120fn wWinMainCRTStartup2() callconv(.c) noreturn {
121 std.posix.exit(callMain());
121 std.process.exit(callMain());
122122}
123123
124124////////////////////////////////////////////////////////////////////////////////
......@@ -627,7 +627,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
627627 for (slice) |func| func();
628628 }
629629
630 std.posix.exit(callMainWithArgs(argc, argv, envp));
630 std.process.exit(callMainWithArgs(argc, argv, envp));
631631}
632632
633633fn expandStackSize(phdrs: []elf.Phdr) void {
......@@ -669,6 +669,11 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
669669 std.os.argv = argv[0..argc];
670670 std.os.environ = envp;
671671
672 if (std.Options.debug_threaded_io) |t| {
673 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
674 t.environ = .{ .block = envp };
675 }
676
672677 std.debug.maybeEnableSegfaultHandler();
673678
674679 return callMain();
......@@ -691,6 +696,11 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal
691696
692697fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {
693698 std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)];
699
700 if (@sizeOf(std.Io.Threaded.Argv0) != 0) {
701 if (std.Options.debug_threaded_io) |t| t.argv0.value = std.os.argv[0];
702 }
703
694704 return callMain();
695705}
696706
lib/std/std.zig+23-3
......@@ -108,14 +108,14 @@ pub const start = @import("start.zig");
108108
109109const root = @import("root");
110110
111/// Stdlib-wide options that can be overridden by the root file.
111/// Compile-time known settings overridable by the root source file.
112112pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options else .{};
113113
114114pub const Options = struct {
115115 enable_segfault_handler: bool = debug.default_enable_segfault_handler,
116116
117 /// Function used to implement `std.fs.cwd` for WASI.
118 wasiCwd: fn () os.wasi.fd_t = fs.defaultWasiCwd,
117 /// Function used to implement `std.Io.Dir.cwd` for WASI.
118 wasiCwd: fn () os.wasi.fd_t = os.defaultWasiCwd,
119119
120120 /// The current log level.
121121 log_level: log.Level = log.default_level,
......@@ -129,6 +129,8 @@ pub const Options = struct {
129129 args: anytype,
130130 ) void = log.defaultLog,
131131
132 logTerminalMode: fn () Io.Terminal.Mode = log.defaultTerminalMode,
133
132134 /// Overrides `std.heap.page_size_min`.
133135 page_size_min: ?usize = null,
134136 /// Overrides `std.heap.page_size_max`.
......@@ -173,6 +175,24 @@ pub const Options = struct {
173175 /// If this is `false`, then captured stack traces will always be empty, and attempts to write
174176 /// stack traces will just print an error to the relevant `Io.Writer` and return.
175177 allow_stack_tracing: bool = !@import("builtin").strip_debug_info,
178
179 pub const debug_threaded_io: ?*Io.Threaded = if (@hasDecl(root, "std_options_debug_threaded_io"))
180 root.std_options_debug_threaded_io
181 else
182 Io.Threaded.global_single_threaded;
183 /// The `Io` instance that `std.debug` uses for `std.debug.print`,
184 /// capturing stack traces, loading debug info, finding the executable's
185 /// own path, and environment variables that affect terminal mode
186 /// detection. The default is to use statically initialized singleton that
187 /// is independent from the application's `Io` instance in order to make
188 /// debugging more straightforward. For example, while debugging an `Io`
189 /// implementation based on coroutines, one likely wants `std.debug.print`
190 /// to directly write to stderr without trying to interact with the code
191 /// being debugged.
192 pub const debug_io: Io = if (@hasDecl(root, "std_options_debug_io")) root.std_options_debug_io else debug_threaded_io.?.ioBasic();
193
194 /// Overrides `std.Io.File.Permissions`.
195 pub const FilePermissions: ?type = if (@hasDecl(root, "std_options_FilePermissions")) root.std_options_FilePermissions else null;
176196};
177197
178198// This forces the start.zig file to be imported, and the comptime logic inside that
lib/std/tar.zig+79-78
......@@ -16,6 +16,7 @@
1616//! pax reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13
1717
1818const std = @import("std");
19const Io = std.Io;
1920const assert = std.debug.assert;
2021const testing = std.testing;
2122
......@@ -302,7 +303,7 @@ pub const FileKind = enum {
302303
303304/// Iterator over entries in the tar file represented by reader.
304305pub const Iterator = struct {
305 reader: *std.Io.Reader,
306 reader: *Io.Reader,
306307 diagnostics: ?*Diagnostics = null,
307308
308309 // buffers for heeader and file attributes
......@@ -328,7 +329,7 @@ pub const Iterator = struct {
328329
329330 /// Iterates over files in tar archive.
330331 /// `next` returns each file in tar archive.
331 pub fn init(reader: *std.Io.Reader, options: Options) Iterator {
332 pub fn init(reader: *Io.Reader, options: Options) Iterator {
332333 return .{
333334 .reader = reader,
334335 .diagnostics = options.diagnostics,
......@@ -473,7 +474,7 @@ pub const Iterator = struct {
473474 return null;
474475 }
475476
476 pub fn streamRemaining(it: *Iterator, file: File, w: *std.Io.Writer) std.Io.Reader.StreamError!void {
477 pub fn streamRemaining(it: *Iterator, file: File, w: *Io.Writer) Io.Reader.StreamError!void {
477478 try it.reader.streamExact64(w, file.size);
478479 it.unread_file_bytes = 0;
479480 }
......@@ -499,14 +500,14 @@ const pax_max_size_attr_len = 64;
499500
500501pub const PaxIterator = struct {
501502 size: usize, // cumulative size of all pax attributes
502 reader: *std.Io.Reader,
503 reader: *Io.Reader,
503504
504505 const Self = @This();
505506
506507 const Attribute = struct {
507508 kind: PaxAttributeKind,
508509 len: usize, // length of the attribute value
509 reader: *std.Io.Reader, // reader positioned at value start
510 reader: *Io.Reader, // reader positioned at value start
510511
511512 // Copies pax attribute value into destination buffer.
512513 // Must be called with destination buffer of size at least Attribute.len.
......@@ -573,13 +574,13 @@ pub const PaxIterator = struct {
573574 }
574575
575576 // Checks that each record ends with new line.
576 fn validateAttributeEnding(reader: *std.Io.Reader) !void {
577 fn validateAttributeEnding(reader: *Io.Reader) !void {
577578 if (try reader.takeByte() != '\n') return error.PaxInvalidAttributeEnd;
578579 }
579580};
580581
581582/// Saves tar file content to the file systems.
582pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOptions) !void {
583pub fn pipeToFileSystem(io: Io, dir: Io.Dir, reader: *Io.Reader, options: PipeOptions) !void {
583584 var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
584585 var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
585586 var file_contents_buffer: [1024]u8 = undefined;
......@@ -605,13 +606,13 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp
605606 switch (file.kind) {
606607 .directory => {
607608 if (file_name.len > 0 and !options.exclude_empty_directories) {
608 try dir.makePath(file_name);
609 try dir.createDirPath(io, file_name);
609610 }
610611 },
611612 .file => {
612 if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| {
613 defer fs_file.close();
614 var file_writer = fs_file.writer(&file_contents_buffer);
613 if (createDirAndFile(io, dir, file_name, filePermissions(file.mode, options))) |fs_file| {
614 defer fs_file.close(io);
615 var file_writer = fs_file.writer(io, &file_contents_buffer);
615616 try it.streamRemaining(file, &file_writer.interface);
616617 try file_writer.interface.flush();
617618 } else |err| {
......@@ -624,7 +625,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp
624625 },
625626 .sym_link => {
626627 const link_name = file.link_name;
627 createDirAndSymlink(dir, link_name, file_name) catch |err| {
628 createDirAndSymlink(io, dir, link_name, file_name) catch |err| {
628629 const d = options.diagnostics orelse return error.UnableToCreateSymLink;
629630 try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{
630631 .code = err,
......@@ -637,12 +638,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp
637638 }
638639}
639640
640fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mode) !std.fs.File {
641 const fs_file = dir.createFile(file_name, .{ .exclusive = true, .mode = mode }) catch |err| {
641fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, permissions: Io.File.Permissions) !Io.File {
642 const fs_file = dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions }) catch |err| {
642643 if (err == error.FileNotFound) {
643644 if (std.fs.path.dirname(file_name)) |dir_name| {
644 try dir.makePath(dir_name);
645 return try dir.createFile(file_name, .{ .exclusive = true, .mode = mode });
645 try dir.createDirPath(io, dir_name);
646 return try dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions });
646647 }
647648 }
648649 return err;
......@@ -651,12 +652,12 @@ fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mo
651652}
652653
653654// Creates a symbolic link at path `file_name` which points to `link_name`.
654fn createDirAndSymlink(dir: std.fs.Dir, link_name: []const u8, file_name: []const u8) !void {
655 dir.symLink(link_name, file_name, .{}) catch |err| {
655fn createDirAndSymlink(io: Io, dir: Io.Dir, link_name: []const u8, file_name: []const u8) !void {
656 dir.symLink(io, link_name, file_name, .{}) catch |err| {
656657 if (err == error.FileNotFound) {
657658 if (std.fs.path.dirname(file_name)) |dir_name| {
658 try dir.makePath(dir_name);
659 return try dir.symLink(link_name, file_name, .{});
659 try dir.createDirPath(io, dir_name);
660 return try dir.symLink(io, link_name, file_name, .{});
660661 }
661662 }
662663 return err;
......@@ -783,7 +784,7 @@ test PaxIterator {
783784 var buffer: [1024]u8 = undefined;
784785
785786 outer: for (cases) |case| {
786 var reader: std.Io.Reader = .fixed(case.data);
787 var reader: Io.Reader = .fixed(case.data);
787788 var it: PaxIterator = .{
788789 .size = case.data.len,
789790 .reader = &reader,
......@@ -874,25 +875,27 @@ test "header parse mode" {
874875}
875876
876877test "create file and symlink" {
878 const io = testing.io;
879
877880 var root = testing.tmpDir(.{});
878881 defer root.cleanup();
879882
880 var file = try createDirAndFile(root.dir, "file1", default_mode);
881 file.close();
882 file = try createDirAndFile(root.dir, "a/b/c/file2", default_mode);
883 file.close();
883 var file = try createDirAndFile(io, root.dir, "file1", .default_file);
884 file.close(io);
885 file = try createDirAndFile(io, root.dir, "a/b/c/file2", .default_file);
886 file.close(io);
884887
885 createDirAndSymlink(root.dir, "a/b/c/file2", "symlink1") catch |err| {
888 createDirAndSymlink(io, root.dir, "a/b/c/file2", "symlink1") catch |err| {
886889 // On Windows when developer mode is not enabled
887890 if (err == error.AccessDenied) return error.SkipZigTest;
888891 return err;
889892 };
890 try createDirAndSymlink(root.dir, "../../../file1", "d/e/f/symlink2");
893 try createDirAndSymlink(io, root.dir, "../../../file1", "d/e/f/symlink2");
891894
892895 // Danglink symlnik, file created later
893 try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3");
894 file = try createDirAndFile(root.dir, "g/h/i/file4", default_mode);
895 file.close();
896 try createDirAndSymlink(io, root.dir, "../../../g/h/i/file4", "j/k/l/symlink3");
897 file = try createDirAndFile(io, root.dir, "g/h/i/file4", .default_file);
898 file.close(io);
896899}
897900
898901test Iterator {
......@@ -916,7 +919,7 @@ test Iterator {
916919 // example/empty/
917920
918921 const data = @embedFile("tar/testdata/example.tar");
919 var reader: std.Io.Reader = .fixed(data);
922 var reader: Io.Reader = .fixed(data);
920923
921924 // User provided buffers to the iterator
922925 var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
......@@ -942,7 +945,7 @@ test Iterator {
942945 .file => {
943946 try testing.expectEqualStrings("example/a/file", file.name);
944947 var buf: [16]u8 = undefined;
945 var w: std.Io.Writer = .fixed(&buf);
948 var w: Io.Writer = .fixed(&buf);
946949 try it.streamRemaining(file, &w);
947950 try testing.expectEqualStrings("content\n", w.buffered());
948951 },
......@@ -955,6 +958,7 @@ test Iterator {
955958}
956959
957960test pipeToFileSystem {
961 const io = testing.io;
958962 // Example tar file is created from this tree structure:
959963 // $ tree example
960964 // example
......@@ -975,14 +979,14 @@ test pipeToFileSystem {
975979 // example/empty/
976980
977981 const data = @embedFile("tar/testdata/example.tar");
978 var reader: std.Io.Reader = .fixed(data);
982 var reader: Io.Reader = .fixed(data);
979983
980984 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
981985 defer tmp.cleanup();
982986 const dir = tmp.dir;
983987
984988 // Save tar from reader to the file system `dir`
985 pipeToFileSystem(dir, &reader, .{
989 pipeToFileSystem(io, dir, &reader, .{
986990 .mode_mode = .ignore,
987991 .strip_components = 1,
988992 .exclude_empty_directories = true,
......@@ -992,21 +996,22 @@ test pipeToFileSystem {
992996 return err;
993997 };
994998
995 try testing.expectError(error.FileNotFound, dir.statFile("empty"));
996 try testing.expect((try dir.statFile("a/file")).kind == .file);
997 try testing.expect((try dir.statFile("b/symlink")).kind == .file); // statFile follows symlink
999 try testing.expectError(error.FileNotFound, dir.statFile(io, "empty", .{}));
1000 try testing.expect((try dir.statFile(io, "a/file", .{})).kind == .file);
1001 try testing.expect((try dir.statFile(io, "b/symlink", .{})).kind == .file); // statFile follows symlink
9981002
9991003 var buf: [32]u8 = undefined;
10001004 try testing.expectEqualSlices(
10011005 u8,
10021006 "../a/file",
1003 normalizePath(try dir.readLink("b/symlink", &buf)),
1007 normalizePath(buf[0..try dir.readLink(io, "b/symlink", &buf)]),
10041008 );
10051009}
10061010
10071011test "pipeToFileSystem root_dir" {
1012 const io = testing.io;
10081013 const data = @embedFile("tar/testdata/example.tar");
1009 var reader: std.Io.Reader = .fixed(data);
1014 var reader: Io.Reader = .fixed(data);
10101015
10111016 // with strip_components = 1
10121017 {
......@@ -1015,7 +1020,7 @@ test "pipeToFileSystem root_dir" {
10151020 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
10161021 defer diagnostics.deinit();
10171022
1018 pipeToFileSystem(tmp.dir, &reader, .{
1023 pipeToFileSystem(io, tmp.dir, &reader, .{
10191024 .strip_components = 1,
10201025 .diagnostics = &diagnostics,
10211026 }) catch |err| {
......@@ -1037,7 +1042,7 @@ test "pipeToFileSystem root_dir" {
10371042 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
10381043 defer diagnostics.deinit();
10391044
1040 pipeToFileSystem(tmp.dir, &reader, .{
1045 pipeToFileSystem(io, tmp.dir, &reader, .{
10411046 .strip_components = 0,
10421047 .diagnostics = &diagnostics,
10431048 }) catch |err| {
......@@ -1053,43 +1058,46 @@ test "pipeToFileSystem root_dir" {
10531058}
10541059
10551060test "findRoot with single file archive" {
1061 const io = testing.io;
10561062 const data = @embedFile("tar/testdata/22752.tar");
1057 var reader: std.Io.Reader = .fixed(data);
1063 var reader: Io.Reader = .fixed(data);
10581064
10591065 var tmp = testing.tmpDir(.{});
10601066 defer tmp.cleanup();
10611067
10621068 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
10631069 defer diagnostics.deinit();
1064 try pipeToFileSystem(tmp.dir, &reader, .{ .diagnostics = &diagnostics });
1070 try pipeToFileSystem(io, tmp.dir, &reader, .{ .diagnostics = &diagnostics });
10651071
10661072 try testing.expectEqualStrings("", diagnostics.root_dir);
10671073}
10681074
10691075test "findRoot without explicit root dir" {
1076 const io = testing.io;
10701077 const data = @embedFile("tar/testdata/19820.tar");
1071 var reader: std.Io.Reader = .fixed(data);
1078 var reader: Io.Reader = .fixed(data);
10721079
10731080 var tmp = testing.tmpDir(.{});
10741081 defer tmp.cleanup();
10751082
10761083 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
10771084 defer diagnostics.deinit();
1078 try pipeToFileSystem(tmp.dir, &reader, .{ .diagnostics = &diagnostics });
1085 try pipeToFileSystem(io, tmp.dir, &reader, .{ .diagnostics = &diagnostics });
10791086
10801087 try testing.expectEqualStrings("root", diagnostics.root_dir);
10811088}
10821089
10831090test "pipeToFileSystem strip_components" {
1091 const io = testing.io;
10841092 const data = @embedFile("tar/testdata/example.tar");
1085 var reader: std.Io.Reader = .fixed(data);
1093 var reader: Io.Reader = .fixed(data);
10861094
10871095 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
10881096 defer tmp.cleanup();
10891097 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
10901098 defer diagnostics.deinit();
10911099
1092 pipeToFileSystem(tmp.dir, &reader, .{
1100 pipeToFileSystem(io, tmp.dir, &reader, .{
10931101 .strip_components = 3,
10941102 .diagnostics = &diagnostics,
10951103 }) catch |err| {
......@@ -1110,45 +1118,36 @@ fn normalizePath(bytes: []u8) []u8 {
11101118 return bytes;
11111119}
11121120
1113const default_mode = std.fs.File.default_mode;
1114
11151121// File system mode based on tar header mode and mode_mode options.
1116fn fileMode(mode: u32, options: PipeOptions) std.fs.File.Mode {
1117 if (!std.fs.has_executable_bit or options.mode_mode == .ignore)
1118 return default_mode;
1119
1120 const S = std.posix.S;
1121
1122 // The mode from the tar file is inspected for the owner executable bit.
1123 if (mode & S.IXUSR == 0)
1124 return default_mode;
1125
1126 // This bit is copied to the group and other executable bits.
1127 // Other bits of the mode are left as the default when creating files.
1128 return default_mode | S.IXUSR | S.IXGRP | S.IXOTH;
1122fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions {
1123 return if (!Io.File.Permissions.has_executable_bit or options.mode_mode == .ignore or (mode & 0o100) == 0)
1124 .default_file
1125 else
1126 .executable_file;
11291127}
11301128
1131test fileMode {
1132 if (!std.fs.has_executable_bit) return error.SkipZigTest;
1133 try testing.expectEqual(default_mode, fileMode(0o744, PipeOptions{ .mode_mode = .ignore }));
1134 try testing.expectEqual(0o777, fileMode(0o744, PipeOptions{}));
1135 try testing.expectEqual(0o666, fileMode(0o644, PipeOptions{}));
1136 try testing.expectEqual(0o666, fileMode(0o655, PipeOptions{}));
1129test filePermissions {
1130 if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest;
1131 try testing.expectEqual(.default_file, filePermissions(0o744, .{ .mode_mode = .ignore }));
1132 try testing.expectEqual(.executable_file, filePermissions(0o744, .{}));
1133 try testing.expectEqual(.default_file, filePermissions(0o644, .{}));
1134 try testing.expectEqual(.default_file, filePermissions(0o655, .{}));
11371135}
11381136
11391137test "executable bit" {
1140 if (!std.fs.has_executable_bit) return error.SkipZigTest;
1138 if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest;
11411139
1140 const io = testing.io;
11421141 const S = std.posix.S;
11431142 const data = @embedFile("tar/testdata/example.tar");
11441143
11451144 for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| {
1146 var reader: std.Io.Reader = .fixed(data);
1145 var reader: Io.Reader = .fixed(data);
11471146
11481147 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
11491148 //defer tmp.cleanup();
11501149
1151 pipeToFileSystem(tmp.dir, &reader, .{
1150 pipeToFileSystem(io, tmp.dir, &reader, .{
11521151 .strip_components = 1,
11531152 .exclude_empty_directories = true,
11541153 .mode_mode = opt,
......@@ -1158,19 +1157,21 @@ test "executable bit" {
11581157 return err;
11591158 };
11601159
1161 const fs = try tmp.dir.statFile("a/file");
1160 const fs = try tmp.dir.statFile(io, "a/file", .{});
11621161 try testing.expect(fs.kind == .file);
11631162
1163 const mode = fs.permissions.toMode();
1164
11641165 if (opt == .executable_bit_only) {
11651166 // Executable bit is set for user, group and others
1166 try testing.expect(fs.mode & S.IXUSR > 0);
1167 try testing.expect(fs.mode & S.IXGRP > 0);
1168 try testing.expect(fs.mode & S.IXOTH > 0);
1167 try testing.expect(mode & S.IXUSR > 0);
1168 try testing.expect(mode & S.IXGRP > 0);
1169 try testing.expect(mode & S.IXOTH > 0);
11691170 }
11701171 if (opt == .ignore) {
1171 try testing.expect(fs.mode & S.IXUSR == 0);
1172 try testing.expect(fs.mode & S.IXGRP == 0);
1173 try testing.expect(fs.mode & S.IXOTH == 0);
1172 try testing.expect(mode & S.IXUSR == 0);
1173 try testing.expect(mode & S.IXGRP == 0);
1174 try testing.expect(mode & S.IXOTH == 0);
11741175 }
11751176 }
11761177}
lib/std/tar/test.zig+7-5
......@@ -424,6 +424,7 @@ test "insufficient buffer in Header name filed" {
424424}
425425
426426test "should not overwrite existing file" {
427 const io = testing.io;
427428 // Starting from this folder structure:
428429 // $ tree root
429430 // root
......@@ -469,17 +470,18 @@ test "should not overwrite existing file" {
469470 defer root.cleanup();
470471 try testing.expectError(
471472 error.PathAlreadyExists,
472 tar.pipeToFileSystem(root.dir, &r, .{ .mode_mode = .ignore, .strip_components = 1 }),
473 tar.pipeToFileSystem(io, root.dir, &r, .{ .mode_mode = .ignore, .strip_components = 1 }),
473474 );
474475
475476 // Unpack with strip_components = 0 should pass
476477 r = .fixed(data);
477478 var root2 = std.testing.tmpDir(.{});
478479 defer root2.cleanup();
479 try tar.pipeToFileSystem(root2.dir, &r, .{ .mode_mode = .ignore, .strip_components = 0 });
480 try tar.pipeToFileSystem(io, root2.dir, &r, .{ .mode_mode = .ignore, .strip_components = 0 });
480481}
481482
482483test "case sensitivity" {
484 const io = testing.io;
483485 // Mimicking issue #18089, this tar contains, same file name in two case
484486 // sensitive name version. Should fail on case insensitive file systems.
485487 //
......@@ -495,13 +497,13 @@ test "case sensitivity" {
495497 var root = std.testing.tmpDir(.{});
496498 defer root.cleanup();
497499
498 tar.pipeToFileSystem(root.dir, &r, .{ .mode_mode = .ignore, .strip_components = 1 }) catch |err| {
500 tar.pipeToFileSystem(io, root.dir, &r, .{ .mode_mode = .ignore, .strip_components = 1 }) catch |err| {
499501 // on case insensitive fs we fail on overwrite existing file
500502 try testing.expectEqual(error.PathAlreadyExists, err);
501503 return;
502504 };
503505
504506 // on case sensitive os both files are created
505 try testing.expect((try root.dir.statFile("alacritty/darkermatrix.yml")).kind == .file);
506 try testing.expect((try root.dir.statFile("alacritty/Darkermatrix.yml")).kind == .file);
507 try testing.expect((try root.dir.statFile(io, "alacritty/darkermatrix.yml", .{})).kind == .file);
508 try testing.expect((try root.dir.statFile(io, "alacritty/Darkermatrix.yml", .{})).kind == .file);
507509}
lib/std/testing.zig+64-56
......@@ -1,5 +1,7 @@
1const std = @import("std.zig");
21const builtin = @import("builtin");
2
3const std = @import("std.zig");
4const Io = std.Io;
35const assert = std.debug.assert;
46const math = std.math;
57
......@@ -28,8 +30,8 @@ pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{
2830 break :b .init;
2931};
3032
31pub var io_instance: std.Io.Threaded = undefined;
32pub const io = io_instance.io();
33pub var io_instance: Io.Threaded = undefined;
34pub const io = if (builtin.is_test) io_instance.io() else @compileError("not testing");
3335
3436/// TODO https://github.com/ziglang/zig/issues/5738
3537pub var log_level = std.log.Level.warn;
......@@ -352,11 +354,10 @@ test expectApproxEqRel {
352354 }
353355}
354356
355/// This function is intended to be used only in tests. When the two slices are not
356/// equal, prints diagnostics to stderr to show exactly how they are not equal (with
357/// the differences highlighted in red), then returns a test failure error.
358/// The colorized output is optional and controlled by the return of `std.Io.tty.Config.detect`.
359/// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead.
357/// This function is intended to be used only in tests. When the two slices are
358/// not equal, prints diagnostics to stderr to show exactly how they are not
359/// equal (with the differences highlighted in red), then returns a test
360/// failure error.
360361pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void {
361362 const diff_index: usize = diff_index: {
362363 const shortest = @min(expected.len, actual.len);
......@@ -367,9 +368,11 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const
367368 break :diff_index if (expected.len == actual.len) return else shortest;
368369 };
369370 if (!backend_can_print) return error.TestExpectedEqual;
370 const stderr_w, const ttyconf = std.debug.lockStderrWriter(&.{});
371 defer std.debug.unlockStderrWriter();
372 failEqualSlices(T, expected, actual, diff_index, stderr_w, ttyconf) catch {};
371 // Intentionally using the debug Io instance rather than the testing Io instance.
372 const stderr = std.debug.lockStderr(&.{});
373 defer std.debug.unlockStderr();
374 const w = &stderr.file_writer.interface;
375 failEqualSlices(T, expected, actual, diff_index, w, stderr.terminal_mode) catch {};
373376 return error.TestExpectedEqual;
374377}
375378
......@@ -378,8 +381,8 @@ fn failEqualSlices(
378381 expected: []const T,
379382 actual: []const T,
380383 diff_index: usize,
381 w: *std.Io.Writer,
382 ttyconf: std.Io.tty.Config,
384 w: *Io.Writer,
385 terminal_mode: Io.Terminal.Mode,
383386) !void {
384387 try w.print("slices differ. first difference occurs at index {d} (0x{X})\n", .{ diff_index, diff_index });
385388
......@@ -402,12 +405,12 @@ fn failEqualSlices(
402405 var differ = if (T == u8) BytesDiffer{
403406 .expected = expected_window,
404407 .actual = actual_window,
405 .ttyconf = ttyconf,
408 .terminal_mode = terminal_mode,
406409 } else SliceDiffer(T){
407410 .start_index = window_start,
408411 .expected = expected_window,
409412 .actual = actual_window,
410 .ttyconf = ttyconf,
413 .terminal_mode = terminal_mode,
411414 };
412415
413416 // Print indexes as hex for slices of u8 since it's more likely to be binary data where
......@@ -464,21 +467,22 @@ fn SliceDiffer(comptime T: type) type {
464467 start_index: usize,
465468 expected: []const T,
466469 actual: []const T,
467 ttyconf: std.Io.tty.Config,
470 terminal_mode: Io.Terminal.Mode,
468471
469472 const Self = @This();
470473
471 pub fn write(self: Self, writer: *std.Io.Writer) !void {
474 pub fn write(self: Self, writer: *Io.Writer) !void {
475 const t: Io.Terminal = .{ .writer = writer, .mode = self.terminal_mode };
472476 for (self.expected, 0..) |value, i| {
473477 const full_index = self.start_index + i;
474478 const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true;
475 if (diff) try self.ttyconf.setColor(writer, .red);
479 if (diff) try t.setColor(.red);
476480 if (@typeInfo(T) == .pointer) {
477481 try writer.print("[{}]{*}: {any}\n", .{ full_index, value, value });
478482 } else {
479483 try writer.print("[{}]: {any}\n", .{ full_index, value });
480484 }
481 if (diff) try self.ttyconf.setColor(writer, .reset);
485 if (diff) try t.setColor(.reset);
482486 }
483487 }
484488 };
......@@ -487,9 +491,9 @@ fn SliceDiffer(comptime T: type) type {
487491const BytesDiffer = struct {
488492 expected: []const u8,
489493 actual: []const u8,
490 ttyconf: std.Io.tty.Config,
494 terminal_mode: Io.Terminal.Mode,
491495
492 pub fn write(self: BytesDiffer, writer: *std.Io.Writer) !void {
496 pub fn write(self: BytesDiffer, writer: *Io.Writer) !void {
493497 var expected_iterator = std.mem.window(u8, self.expected, 16, 16);
494498 var row: usize = 0;
495499 while (expected_iterator.next()) |chunk| {
......@@ -514,7 +518,7 @@ const BytesDiffer = struct {
514518 try self.writeDiff(writer, "{c}", .{byte}, diff);
515519 } else {
516520 // TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed
517 if (self.ttyconf == .windows_api) {
521 if (self.terminal_mode == .windows_api) {
518522 try self.writeDiff(writer, ".", .{}, diff);
519523 continue;
520524 }
......@@ -535,10 +539,14 @@ const BytesDiffer = struct {
535539 }
536540 }
537541
538 fn writeDiff(self: BytesDiffer, writer: *std.Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void {
539 if (diff) try self.ttyconf.setColor(writer, .red);
542 fn terminal(self: *const BytesDiffer, writer: *Io.Writer) Io.Terminal {
543 return .{ .writer = writer, .mode = self.terminal_mode };
544 }
545
546 fn writeDiff(self: BytesDiffer, writer: *Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void {
547 if (diff) try self.terminal(writer).setColor(.red);
540548 try writer.print(fmt, args);
541 if (diff) try self.ttyconf.setColor(writer, .reset);
549 if (diff) try self.terminal(writer).setColor(.reset);
542550 }
543551};
544552
......@@ -605,34 +613,35 @@ pub fn expect(ok: bool) !void {
605613}
606614
607615pub const TmpDir = struct {
608 dir: std.fs.Dir,
609 parent_dir: std.fs.Dir,
616 dir: Io.Dir,
617 parent_dir: Io.Dir,
610618 sub_path: [sub_path_len]u8,
611619
612620 const random_bytes_count = 12;
613621 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
614622
615623 pub fn cleanup(self: *TmpDir) void {
616 self.dir.close();
617 self.parent_dir.deleteTree(&self.sub_path) catch {};
618 self.parent_dir.close();
624 self.dir.close(io);
625 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
626 self.parent_dir.close(io);
619627 self.* = undefined;
620628 }
621629};
622630
623pub fn tmpDir(opts: std.fs.Dir.OpenOptions) TmpDir {
631pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir {
632 comptime assert(builtin.is_test);
624633 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
625634 std.crypto.random.bytes(&random_bytes);
626635 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
627636 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
628637
629 const cwd = std.fs.cwd();
630 var cache_dir = cwd.makeOpenPath(".zig-cache", .{}) catch
638 const cwd = Io.Dir.cwd();
639 var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch
631640 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");
632 defer cache_dir.close();
633 const parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch
641 defer cache_dir.close(io);
642 const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch
634643 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir");
635 const dir = parent_dir.makeOpenPath(&sub_path, opts) catch
644 const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch
636645 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
637646
638647 return .{
......@@ -929,7 +938,7 @@ test "expectEqualDeep primitive type" {
929938 a,
930939 b,
931940
932 pub fn format(self: @This(), writer: *std.Io.Writer) !void {
941 pub fn format(self: @This(), writer: *Io.Writer) !void {
933942 try writer.writeAll(@tagName(self));
934943 }
935944 };
......@@ -1146,9 +1155,10 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
11461155 break :x failing_allocator_inst.alloc_index;
11471156 };
11481157
1149 var fail_index: usize = 0;
1150 while (fail_index < needed_alloc_count) : (fail_index += 1) {
1151 var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ .fail_index = fail_index });
1158 for (0..needed_alloc_count) |fail_index| {
1159 var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{
1160 .fail_index = fail_index,
1161 });
11521162 args.@"0" = failing_allocator_inst.allocator();
11531163
11541164 if (@call(.auto, test_fn, args)) |_| {
......@@ -1160,7 +1170,6 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
11601170 } else |err| switch (err) {
11611171 error.OutOfMemory => {
11621172 if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {
1163 const tty_config: std.Io.tty.Config = .detect(.stderr());
11641173 print(
11651174 "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}",
11661175 .{
......@@ -1172,7 +1181,6 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
11721181 failing_allocator_inst.deallocations,
11731182 std.debug.FormatStackTrace{
11741183 .stack_trace = failing_allocator_inst.getStackTrace(),
1175 .tty_config = tty_config,
11761184 },
11771185 },
11781186 );
......@@ -1220,14 +1228,14 @@ pub inline fn fuzz(
12201228 return @import("root").fuzz(context, testOne, options);
12211229}
12221230
1223/// A `std.Io.Reader` that writes a predetermined list of buffers during `stream`.
1231/// A `Io.Reader` that writes a predetermined list of buffers during `stream`.
12241232pub const Reader = struct {
12251233 calls: []const Call,
1226 interface: std.Io.Reader,
1234 interface: Io.Reader,
12271235 next_call_index: usize,
12281236 next_offset: usize,
12291237 /// Further reduces how many bytes are written in each `stream` call.
1230 artificial_limit: std.Io.Limit = .unlimited,
1238 artificial_limit: Io.Limit = .unlimited,
12311239
12321240 pub const Call = struct {
12331241 buffer: []const u8,
......@@ -1247,7 +1255,7 @@ pub const Reader = struct {
12471255 };
12481256 }
12491257
1250 fn stream(io_r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
1258 fn stream(io_r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
12511259 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
12521260 if (r.calls.len - r.next_call_index == 0) return error.EndOfStream;
12531261 const call = r.calls[r.next_call_index];
......@@ -1262,13 +1270,13 @@ pub const Reader = struct {
12621270 }
12631271};
12641272
1265/// A `std.Io.Reader` that gets its data from another `std.Io.Reader`, and always
1273/// A `Io.Reader` that gets its data from another `Io.Reader`, and always
12661274/// writes to its own buffer (and returns 0) during `stream` and `readVec`.
12671275pub const ReaderIndirect = struct {
1268 in: *std.Io.Reader,
1269 interface: std.Io.Reader,
1276 in: *Io.Reader,
1277 interface: Io.Reader,
12701278
1271 pub fn init(in: *std.Io.Reader, buffer: []u8) ReaderIndirect {
1279 pub fn init(in: *Io.Reader, buffer: []u8) ReaderIndirect {
12721280 return .{
12731281 .in = in,
12741282 .interface = .{
......@@ -1283,17 +1291,17 @@ pub const ReaderIndirect = struct {
12831291 };
12841292 }
12851293
1286 fn readVec(r: *std.Io.Reader, _: [][]u8) std.Io.Reader.Error!usize {
1294 fn readVec(r: *Io.Reader, _: [][]u8) Io.Reader.Error!usize {
12871295 try streamInner(r);
12881296 return 0;
12891297 }
12901298
1291 fn stream(r: *std.Io.Reader, _: *std.Io.Writer, _: std.Io.Limit) std.Io.Reader.StreamError!usize {
1299 fn stream(r: *Io.Reader, _: *Io.Writer, _: Io.Limit) Io.Reader.StreamError!usize {
12921300 try streamInner(r);
12931301 return 0;
12941302 }
12951303
1296 fn streamInner(r: *std.Io.Reader) std.Io.Reader.Error!void {
1304 fn streamInner(r: *Io.Reader) Io.Reader.Error!void {
12971305 const r_indirect: *ReaderIndirect = @alignCast(@fieldParentPtr("interface", r));
12981306
12991307 // If there's no room remaining in the buffer at all, make room.
......@@ -1301,12 +1309,12 @@ pub const ReaderIndirect = struct {
13011309 try r.rebase(r.buffer.len);
13021310 }
13031311
1304 var writer: std.Io.Writer = .{
1312 var writer: Io.Writer = .{
13051313 .buffer = r.buffer,
13061314 .end = r.end,
13071315 .vtable = &.{
1308 .drain = std.Io.Writer.unreachableDrain,
1309 .rebase = std.Io.Writer.unreachableRebase,
1316 .drain = Io.Writer.unreachableDrain,
1317 .rebase = Io.Writer.unreachableRebase,
13101318 },
13111319 };
13121320 defer r.end = writer.end;
lib/std/unicode/throughput_test.zig+3-3
......@@ -1,8 +1,8 @@
11const std = @import("std");
2const Io = std.Io;
23const time = std.time;
34const unicode = std.unicode;
4
5const Timer = time.Timer;
5const Timer = std.time.Timer;
66
77const N = 1_000_000;
88
......@@ -41,7 +41,7 @@ fn benchmarkCodepointCount(buf: []const u8) !ResultCount {
4141pub fn main() !void {
4242 // Size of buffer is about size of printed message.
4343 var stdout_buffer: [0x100]u8 = undefined;
44 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
44 var stdout_writer = Io.File.stdout().writer(&stdout_buffer);
4545 const stdout = &stdout_writer.interface;
4646
4747 try stdout.print("short ASCII strings\n", .{});
lib/std/zig.zig+7-14
......@@ -46,23 +46,16 @@ pub const SrcHasher = std.crypto.hash.Blake3;
4646pub const SrcHash = [16]u8;
4747
4848pub const Color = enum {
49 /// Determine whether stderr is a terminal or not automatically.
49 /// Auto-detect whether stream supports terminal colors.
5050 auto,
51 /// Assume stderr is not a terminal.
51 /// Force-enable colors.
5252 off,
53 /// Assume stderr is a terminal.
53 /// Suppress colors.
5454 on,
5555
56 pub fn getTtyConf(color: Color, detected: Io.tty.Config) Io.tty.Config {
56 pub fn terminalMode(color: Color) ?Io.Terminal.Mode {
5757 return switch (color) {
58 .auto => detected,
59 .on => .escape_codes,
60 .off => .no_color,
61 };
62 }
63 pub fn detectTtyConf(color: Color) Io.tty.Config {
64 return switch (color) {
65 .auto => .detect(.stderr()),
58 .auto => null,
6659 .on => .escape_codes,
6760 .off => .no_color,
6861 };
......@@ -639,7 +632,7 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![
639632 return buffer.toOwnedSliceSentinel(gpa, 0);
640633}
641634
642pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {
635pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u8, color: Color) !void {
643636 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
644637 try wip_errors.init(gpa);
645638 defer wip_errors.deinit();
......@@ -648,7 +641,7 @@ pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color
648641
649642 var error_bundle = try wip_errors.toOwnedBundle("");
650643 defer error_bundle.deinit(gpa);
651 error_bundle.renderToStdErr(.{}, color);
644 return error_bundle.renderToStderr(io, .{}, color);
652645}
653646
654647pub fn putAstErrorsIntoBundle(
lib/std/zig/ErrorBundle.zig+44-34
......@@ -162,45 +162,57 @@ pub const RenderOptions = struct {
162162 include_log_text: bool = true,
163163};
164164
165pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions, color: std.zig.Color) void {
165pub const RenderToStderrError = Io.Cancelable || Io.File.Writer.Error;
166
167pub fn renderToStderr(eb: ErrorBundle, io: Io, options: RenderOptions, color: std.zig.Color) RenderToStderrError!void {
166168 var buffer: [256]u8 = undefined;
167 const w, const ttyconf = std.debug.lockStderrWriter(&buffer);
168 defer std.debug.unlockStderrWriter();
169 renderToWriter(eb, options, w, color.getTtyConf(ttyconf)) catch return;
169 const stderr = try io.lockStderr(&buffer, color.terminalMode());
170 defer io.unlockStderr();
171 renderToTerminal(eb, options, stderr.terminal()) catch |err| switch (err) {
172 error.WriteFailed => return stderr.file_writer.err.?,
173 else => |e| return e,
174 };
175}
176
177pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, w: *Writer) Writer.Error!void {
178 return renderToTerminal(eb, options, .{ .writer = w, .mode = .no_color }) catch |err| switch (err) {
179 error.WriteFailed => |e| return e,
180 else => unreachable,
181 };
170182}
171183
172pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, w: *Writer, ttyconf: Io.tty.Config) (Writer.Error || std.posix.UnexpectedError)!void {
184pub fn renderToTerminal(eb: ErrorBundle, options: RenderOptions, t: Io.Terminal) Io.Terminal.SetColorError!void {
173185 if (eb.extra.len == 0) return;
174186 for (eb.getMessages()) |err_msg| {
175 try renderErrorMessageToWriter(eb, options, err_msg, w, ttyconf, "error", .red, 0);
187 try renderErrorMessage(eb, options, err_msg, t, "error", .red, 0);
176188 }
177189
178190 if (options.include_log_text) {
179191 const log_text = eb.getCompileLogOutput();
180192 if (log_text.len != 0) {
181 try w.writeAll("\nCompile Log Output:\n");
182 try w.writeAll(log_text);
193 try t.writer.writeAll("\nCompile Log Output:\n");
194 try t.writer.writeAll(log_text);
183195 }
184196 }
185197}
186198
187fn renderErrorMessageToWriter(
199fn renderErrorMessage(
188200 eb: ErrorBundle,
189201 options: RenderOptions,
190202 err_msg_index: MessageIndex,
191 w: *Writer,
192 ttyconf: Io.tty.Config,
203 t: Io.Terminal,
193204 kind: []const u8,
194 color: Io.tty.Color,
205 color: Io.Terminal.Color,
195206 indent: usize,
196) (Writer.Error || std.posix.UnexpectedError)!void {
207) Io.Terminal.SetColorError!void {
208 const w = t.writer;
197209 const err_msg = eb.getErrorMessage(err_msg_index);
198210 if (err_msg.src_loc != .none) {
199211 const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc));
200212 var prefix: Writer.Discarding = .init(&.{});
201213 try w.splatByteAll(' ', indent);
202214 prefix.count += indent;
203 try ttyconf.setColor(w, .bold);
215 try t.setColor(.bold);
204216 try w.print("{s}:{d}:{d}: ", .{
205217 eb.nullTerminatedString(src.data.src_path),
206218 src.data.line + 1,
......@@ -211,7 +223,7 @@ fn renderErrorMessageToWriter(
211223 src.data.line + 1,
212224 src.data.column + 1,
213225 });
214 try ttyconf.setColor(w, color);
226 try t.setColor(color);
215227 try w.writeAll(kind);
216228 prefix.count += kind.len;
217229 try w.writeAll(": ");
......@@ -219,17 +231,17 @@ fn renderErrorMessageToWriter(
219231 // This is the length of the part before the error message:
220232 // e.g. "file.zig:4:5: error: "
221233 const prefix_len: usize = @intCast(prefix.count);
222 try ttyconf.setColor(w, .reset);
223 try ttyconf.setColor(w, .bold);
234 try t.setColor(.reset);
235 try t.setColor(.bold);
224236 if (err_msg.count == 1) {
225237 try writeMsg(eb, err_msg, w, prefix_len);
226238 try w.writeByte('\n');
227239 } else {
228240 try writeMsg(eb, err_msg, w, prefix_len);
229 try ttyconf.setColor(w, .dim);
241 try t.setColor(.dim);
230242 try w.print(" ({d} times)\n", .{err_msg.count});
231243 }
232 try ttyconf.setColor(w, .reset);
244 try t.setColor(.reset);
233245 if (src.data.source_line != 0 and options.include_source_line) {
234246 const line = eb.nullTerminatedString(src.data.source_line);
235247 for (line) |b| switch (b) {
......@@ -242,19 +254,19 @@ fn renderErrorMessageToWriter(
242254 // -1 since span.main includes the caret
243255 const after_caret = src.data.span_end -| src.data.span_main -| 1;
244256 try w.splatByteAll(' ', src.data.column - before_caret);
245 try ttyconf.setColor(w, .green);
257 try t.setColor(.green);
246258 try w.splatByteAll('~', before_caret);
247259 try w.writeByte('^');
248260 try w.splatByteAll('~', after_caret);
249261 try w.writeByte('\n');
250 try ttyconf.setColor(w, .reset);
262 try t.setColor(.reset);
251263 }
252264 for (eb.getNotes(err_msg_index)) |note| {
253 try renderErrorMessageToWriter(eb, options, note, w, ttyconf, "note", .cyan, indent);
265 try renderErrorMessage(eb, options, note, t, "note", .cyan, indent);
254266 }
255267 if (src.data.reference_trace_len > 0 and options.include_reference_trace) {
256 try ttyconf.setColor(w, .reset);
257 try ttyconf.setColor(w, .dim);
268 try t.setColor(.reset);
269 try t.setColor(.dim);
258270 try w.print("referenced by:\n", .{});
259271 var ref_index = src.end;
260272 for (0..src.data.reference_trace_len) |_| {
......@@ -281,25 +293,25 @@ fn renderErrorMessageToWriter(
281293 );
282294 }
283295 }
284 try ttyconf.setColor(w, .reset);
296 try t.setColor(.reset);
285297 }
286298 } else {
287 try ttyconf.setColor(w, color);
299 try t.setColor(color);
288300 try w.splatByteAll(' ', indent);
289301 try w.writeAll(kind);
290302 try w.writeAll(": ");
291 try ttyconf.setColor(w, .reset);
303 try t.setColor(.reset);
292304 const msg = eb.nullTerminatedString(err_msg.msg);
293305 if (err_msg.count == 1) {
294306 try w.print("{s}\n", .{msg});
295307 } else {
296308 try w.print("{s}", .{msg});
297 try ttyconf.setColor(w, .dim);
309 try t.setColor(.dim);
298310 try w.print(" ({d} times)\n", .{err_msg.count});
299311 }
300 try ttyconf.setColor(w, .reset);
312 try t.setColor(.reset);
301313 for (eb.getNotes(err_msg_index)) |note| {
302 try renderErrorMessageToWriter(eb, options, note, w, ttyconf, "note", .cyan, indent + 4);
314 try renderErrorMessage(eb, options, note, t, "note", .cyan, indent + 4);
303315 }
304316 }
305317}
......@@ -806,12 +818,10 @@ pub const Wip = struct {
806818 };
807819 defer bundle.deinit(std.testing.allocator);
808820
809 const ttyconf: Io.tty.Config = .no_color;
810
811821 var bundle_buf: Writer.Allocating = .init(std.testing.allocator);
812822 const bundle_bw = &bundle_buf.interface;
813823 defer bundle_buf.deinit();
814 try bundle.renderToWriter(.{ .ttyconf = ttyconf }, bundle_bw);
824 try bundle.renderToWriter(bundle_bw);
815825
816826 var copy = copy: {
817827 var wip: ErrorBundle.Wip = undefined;
......@@ -827,7 +837,7 @@ pub const Wip = struct {
827837 var copy_buf: Writer.Allocating = .init(std.testing.allocator);
828838 const copy_bw = &copy_buf.interface;
829839 defer copy_buf.deinit();
830 try copy.renderToWriter(.{ .ttyconf = ttyconf }, copy_bw);
840 try copy.renderToWriter(copy_bw);
831841
832842 try std.testing.expectEqualStrings(bundle_bw.written(), copy_bw.written());
833843 }
lib/std/zig/LibCDirs.zig+11-8
......@@ -1,3 +1,11 @@
1const LibCDirs = @This();
2const builtin = @import("builtin");
3
4const std = @import("../std.zig");
5const Io = std.Io;
6const LibCInstallation = std.zig.LibCInstallation;
7const Allocator = std.mem.Allocator;
8
19libc_include_dir_list: []const []const u8,
210libc_installation: ?*const LibCInstallation,
311libc_framework_dir_list: []const []const u8,
......@@ -14,6 +22,7 @@ pub const DarwinSdkLayout = enum {
1422
1523pub fn detect(
1624 arena: Allocator,
25 io: Io,
1726 zig_lib_dir: []const u8,
1827 target: *const std.Target,
1928 is_native_abi: bool,
......@@ -38,7 +47,7 @@ pub fn detect(
3847 // using the system libc installation.
3948 if (is_native_abi and !target.isMinGW()) {
4049 const libc = try arena.create(LibCInstallation);
41 libc.* = LibCInstallation.findNative(.{ .allocator = arena, .target = target }) catch |err| switch (err) {
50 libc.* = LibCInstallation.findNative(arena, io, .{ .target = target }) catch |err| switch (err) {
4251 error.CCompilerExitCode,
4352 error.CCompilerCrashed,
4453 error.CCompilerCannotFindHeaders,
......@@ -75,7 +84,7 @@ pub fn detect(
7584
7685 if (use_system_abi) {
7786 const libc = try arena.create(LibCInstallation);
78 libc.* = try LibCInstallation.findNative(.{ .allocator = arena, .verbose = true, .target = target });
87 libc.* = try LibCInstallation.findNative(arena, io, .{ .verbose = true, .target = target });
7988 return detectFromInstallation(arena, target, libc);
8089 }
8190
......@@ -265,9 +274,3 @@ fn libCGenericName(target: *const std.Target) [:0]const u8 {
265274 => unreachable,
266275 }
267276}
268
269const LibCDirs = @This();
270const builtin = @import("builtin");
271const std = @import("../std.zig");
272const LibCInstallation = std.zig.LibCInstallation;
273const Allocator = std.mem.Allocator;
lib/std/zig/LibCInstallation.zig+94-111
......@@ -1,4 +1,18 @@
11//! See the render function implementation for documentation of the fields.
2const LibCInstallation = @This();
3
4const builtin = @import("builtin");
5const is_darwin = builtin.target.os.tag.isDarwin();
6const is_windows = builtin.target.os.tag == .windows;
7const is_haiku = builtin.target.os.tag == .haiku;
8
9const std = @import("std");
10const Io = std.Io;
11const Target = std.Target;
12const fs = std.fs;
13const Allocator = std.mem.Allocator;
14const Path = std.Build.Cache.Path;
15const log = std.log.scoped(.libc_installation);
216
317include_dir: ?[]const u8 = null,
418sys_include_dir: ?[]const u8 = null,
......@@ -23,11 +37,7 @@ pub const FindError = error{
2337 ZigIsTheCCompiler,
2438};
2539
26pub fn parse(
27 allocator: Allocator,
28 libc_file: []const u8,
29 target: *const std.Target,
30) !LibCInstallation {
40pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const std.Target) !LibCInstallation {
3141 var self: LibCInstallation = .{};
3242
3343 const fields = std.meta.fields(LibCInstallation);
......@@ -43,7 +53,7 @@ pub fn parse(
4353 }
4454 }
4555
46 const contents = try std.fs.cwd().readFileAlloc(libc_file, allocator, .limited(std.math.maxInt(usize)));
56 const contents = try Io.Dir.cwd().readFileAlloc(io, libc_file, allocator, .limited(std.math.maxInt(usize)));
4757 defer allocator.free(contents);
4858
4959 var it = std.mem.tokenizeScalar(u8, contents, '\n');
......@@ -156,7 +166,6 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {
156166}
157167
158168pub const FindNativeOptions = struct {
159 allocator: Allocator,
160169 target: *const std.Target,
161170
162171 /// If enabled, will print human-friendly errors to stderr.
......@@ -164,50 +173,50 @@ pub const FindNativeOptions = struct {
164173};
165174
166175/// Finds the default, native libc.
167pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation {
176pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!LibCInstallation {
168177 var self: LibCInstallation = .{};
169178
170179 if (is_darwin and args.target.os.tag.isDarwin()) {
171 if (!std.zig.system.darwin.isSdkInstalled(args.allocator))
180 if (!std.zig.system.darwin.isSdkInstalled(gpa, io))
172181 return error.DarwinSdkNotFound;
173 const sdk = std.zig.system.darwin.getSdk(args.allocator, args.target) orelse
182 const sdk = std.zig.system.darwin.getSdk(gpa, io, args.target) orelse
174183 return error.DarwinSdkNotFound;
175 defer args.allocator.free(sdk);
184 defer gpa.free(sdk);
176185
177 self.include_dir = try fs.path.join(args.allocator, &.{
186 self.include_dir = try fs.path.join(gpa, &.{
178187 sdk, "usr/include",
179188 });
180 self.sys_include_dir = try fs.path.join(args.allocator, &.{
189 self.sys_include_dir = try fs.path.join(gpa, &.{
181190 sdk, "usr/include",
182191 });
183192 return self;
184193 } else if (is_windows) {
185 const sdk = std.zig.WindowsSdk.find(args.allocator, args.target.cpu.arch) catch |err| switch (err) {
194 const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch) catch |err| switch (err) {
186195 error.NotFound => return error.WindowsSdkNotFound,
187196 error.PathTooLong => return error.WindowsSdkNotFound,
188197 error.OutOfMemory => return error.OutOfMemory,
189198 };
190 defer sdk.free(args.allocator);
199 defer sdk.free(gpa);
191200
192 try self.findNativeMsvcIncludeDir(args, sdk);
193 try self.findNativeMsvcLibDir(args, sdk);
194 try self.findNativeKernel32LibDir(args, sdk);
195 try self.findNativeIncludeDirWindows(args, sdk);
196 try self.findNativeCrtDirWindows(args, sdk);
201 try self.findNativeMsvcIncludeDir(gpa, io, sdk);
202 try self.findNativeMsvcLibDir(gpa, sdk);
203 try self.findNativeKernel32LibDir(gpa, io, args, sdk);
204 try self.findNativeIncludeDirWindows(gpa, io, sdk);
205 try self.findNativeCrtDirWindows(gpa, io, args.target, sdk);
197206 } else if (is_haiku) {
198 try self.findNativeIncludeDirPosix(args);
199 try self.findNativeGccDirHaiku(args);
200 self.crt_dir = try args.allocator.dupeZ(u8, "/system/develop/lib");
207 try self.findNativeIncludeDirPosix(gpa, io, args);
208 try self.findNativeGccDirHaiku(gpa, io, args);
209 self.crt_dir = try gpa.dupeZ(u8, "/system/develop/lib");
201210 } else if (builtin.target.os.tag == .illumos) {
202211 // There is only one libc, and its headers/libraries are always in the same spot.
203 self.include_dir = try args.allocator.dupeZ(u8, "/usr/include");
204 self.sys_include_dir = try args.allocator.dupeZ(u8, "/usr/include");
205 self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib/64");
212 self.include_dir = try gpa.dupeZ(u8, "/usr/include");
213 self.sys_include_dir = try gpa.dupeZ(u8, "/usr/include");
214 self.crt_dir = try gpa.dupeZ(u8, "/usr/lib/64");
206215 } else if (std.process.can_spawn) {
207 try self.findNativeIncludeDirPosix(args);
216 try self.findNativeIncludeDirPosix(gpa, io, args);
208217 switch (builtin.target.os.tag) {
209 .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib"),
210 .linux => try self.findNativeCrtDirPosix(args),
218 .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try gpa.dupeZ(u8, "/usr/lib"),
219 .linux => try self.findNativeCrtDirPosix(gpa, io, args),
211220 else => {},
212221 }
213222 } else {
......@@ -227,11 +236,9 @@ pub fn deinit(self: *LibCInstallation, allocator: Allocator) void {
227236 self.* = undefined;
228237}
229238
230fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void {
231 const allocator = args.allocator;
232
239fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void {
233240 // Detect infinite loops.
234 var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
241 var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) {
235242 error.Unexpected => unreachable, // WASI-only
236243 else => |e| return e,
237244 };
......@@ -250,7 +257,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
250257
251258 const dev_null = if (is_windows) "nul" else "/dev/null";
252259
253 var argv = std.array_list.Managed([]const u8).init(allocator);
260 var argv = std.array_list.Managed([]const u8).init(gpa);
254261 defer argv.deinit();
255262
256263 try appendCcExe(&argv, skip_cc_env_var);
......@@ -261,8 +268,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
261268 dev_null,
262269 });
263270
264 const run_res = std.process.Child.run(.{
265 .allocator = allocator,
271 const run_res = std.process.Child.run(gpa, io, .{
266272 .argv = argv.items,
267273 .max_output_bytes = 1024 * 1024,
268274 .env_map = &env_map,
......@@ -279,8 +285,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
279285 },
280286 };
281287 defer {
282 allocator.free(run_res.stdout);
283 allocator.free(run_res.stderr);
288 gpa.free(run_res.stdout);
289 gpa.free(run_res.stderr);
284290 }
285291 switch (run_res.term) {
286292 .Exited => |code| if (code != 0) {
......@@ -294,7 +300,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
294300 }
295301
296302 var it = std.mem.tokenizeAny(u8, run_res.stderr, "\n\r");
297 var search_paths = std.array_list.Managed([]const u8).init(allocator);
303 var search_paths = std.array_list.Managed([]const u8).init(gpa);
298304 defer search_paths.deinit();
299305 while (it.next()) |line| {
300306 if (line.len != 0 and line[0] == ' ') {
......@@ -318,7 +324,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
318324 // search in reverse order
319325 const search_path_untrimmed = search_paths.items[search_paths.items.len - path_i - 1];
320326 const search_path = std.mem.trimStart(u8, search_path_untrimmed, " ");
321 var search_dir = fs.cwd().openDir(search_path, .{}) catch |err| switch (err) {
327 var search_dir = Io.Dir.cwd().openDir(io, search_path, .{}) catch |err| switch (err) {
322328 error.FileNotFound,
323329 error.NotDir,
324330 error.NoDevice,
......@@ -326,11 +332,11 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
326332
327333 else => return error.FileSystem,
328334 };
329 defer search_dir.close();
335 defer search_dir.close(io);
330336
331337 if (self.include_dir == null) {
332 if (search_dir.access(include_dir_example_file, .{})) |_| {
333 self.include_dir = try allocator.dupeZ(u8, search_path);
338 if (search_dir.access(io, include_dir_example_file, .{})) |_| {
339 self.include_dir = try gpa.dupeZ(u8, search_path);
334340 } else |err| switch (err) {
335341 error.FileNotFound => {},
336342 else => return error.FileSystem,
......@@ -338,8 +344,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
338344 }
339345
340346 if (self.sys_include_dir == null) {
341 if (search_dir.access(sys_include_dir_example_file, .{})) |_| {
342 self.sys_include_dir = try allocator.dupeZ(u8, search_path);
347 if (search_dir.access(io, sys_include_dir_example_file, .{})) |_| {
348 self.sys_include_dir = try gpa.dupeZ(u8, search_path);
343349 } else |err| switch (err) {
344350 error.FileNotFound => {},
345351 else => return error.FileSystem,
......@@ -357,22 +363,21 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
357363
358364fn findNativeIncludeDirWindows(
359365 self: *LibCInstallation,
360 args: FindNativeOptions,
366 gpa: Allocator,
367 io: Io,
361368 sdk: std.zig.WindowsSdk,
362369) FindError!void {
363 const allocator = args.allocator;
364
365370 var install_buf: [2]std.zig.WindowsSdk.Installation = undefined;
366371 const installs = fillInstallations(&install_buf, sdk);
367372
368 var result_buf = std.array_list.Managed(u8).init(allocator);
373 var result_buf = std.array_list.Managed(u8).init(gpa);
369374 defer result_buf.deinit();
370375
371376 for (installs) |install| {
372377 result_buf.shrinkAndFree(0);
373378 try result_buf.print("{s}\\Include\\{s}\\ucrt", .{ install.path, install.version });
374379
375 var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) {
380 var dir = Io.Dir.cwd().openDir(io, result_buf.items, .{}) catch |err| switch (err) {
376381 error.FileNotFound,
377382 error.NotDir,
378383 error.NoDevice,
......@@ -380,9 +385,9 @@ fn findNativeIncludeDirWindows(
380385
381386 else => return error.FileSystem,
382387 };
383 defer dir.close();
388 defer dir.close(io);
384389
385 dir.access("stdlib.h", .{}) catch |err| switch (err) {
390 dir.access(io, "stdlib.h", .{}) catch |err| switch (err) {
386391 error.FileNotFound => continue,
387392 else => return error.FileSystem,
388393 };
......@@ -396,18 +401,18 @@ fn findNativeIncludeDirWindows(
396401
397402fn findNativeCrtDirWindows(
398403 self: *LibCInstallation,
399 args: FindNativeOptions,
404 gpa: Allocator,
405 io: Io,
406 target: *const std.Target,
400407 sdk: std.zig.WindowsSdk,
401408) FindError!void {
402 const allocator = args.allocator;
403
404409 var install_buf: [2]std.zig.WindowsSdk.Installation = undefined;
405410 const installs = fillInstallations(&install_buf, sdk);
406411
407 var result_buf = std.array_list.Managed(u8).init(allocator);
412 var result_buf = std.array_list.Managed(u8).init(gpa);
408413 defer result_buf.deinit();
409414
410 const arch_sub_dir = switch (args.target.cpu.arch) {
415 const arch_sub_dir = switch (target.cpu.arch) {
411416 .x86 => "x86",
412417 .x86_64 => "x64",
413418 .arm, .armeb => "arm",
......@@ -419,7 +424,7 @@ fn findNativeCrtDirWindows(
419424 result_buf.shrinkAndFree(0);
420425 try result_buf.print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ install.path, install.version, arch_sub_dir });
421426
422 var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) {
427 var dir = Io.Dir.cwd().openDir(io, result_buf.items, .{}) catch |err| switch (err) {
423428 error.FileNotFound,
424429 error.NotDir,
425430 error.NoDevice,
......@@ -427,9 +432,9 @@ fn findNativeCrtDirWindows(
427432
428433 else => return error.FileSystem,
429434 };
430 defer dir.close();
435 defer dir.close(io);
431436
432 dir.access("ucrt.lib", .{}) catch |err| switch (err) {
437 dir.access(io, "ucrt.lib", .{}) catch |err| switch (err) {
433438 error.FileNotFound => continue,
434439 else => return error.FileSystem,
435440 };
......@@ -440,9 +445,8 @@ fn findNativeCrtDirWindows(
440445 return error.LibCRuntimeNotFound;
441446}
442447
443fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void {
444 self.crt_dir = try ccPrintFileName(.{
445 .allocator = args.allocator,
448fn findNativeCrtDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void {
449 self.crt_dir = try ccPrintFileName(gpa, io, .{
446450 .search_basename = switch (args.target.os.tag) {
447451 .linux => if (args.target.abi.isAndroid()) "crtbegin_dynamic.o" else "crt1.o",
448452 else => "crt1.o",
......@@ -452,9 +456,8 @@ fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindE
452456 });
453457}
454458
455fn findNativeGccDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindError!void {
456 self.gcc_dir = try ccPrintFileName(.{
457 .allocator = args.allocator,
459fn findNativeGccDirHaiku(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void {
460 self.gcc_dir = try ccPrintFileName(gpa, io, .{
458461 .search_basename = "crtbeginS.o",
459462 .want_dirname = .only_dir,
460463 .verbose = args.verbose,
......@@ -463,15 +466,15 @@ fn findNativeGccDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindE
463466
464467fn findNativeKernel32LibDir(
465468 self: *LibCInstallation,
469 gpa: Allocator,
470 io: Io,
466471 args: FindNativeOptions,
467472 sdk: std.zig.WindowsSdk,
468473) FindError!void {
469 const allocator = args.allocator;
470
471474 var install_buf: [2]std.zig.WindowsSdk.Installation = undefined;
472475 const installs = fillInstallations(&install_buf, sdk);
473476
474 var result_buf = std.array_list.Managed(u8).init(allocator);
477 var result_buf = std.array_list.Managed(u8).init(gpa);
475478 defer result_buf.deinit();
476479
477480 const arch_sub_dir = switch (args.target.cpu.arch) {
......@@ -486,7 +489,7 @@ fn findNativeKernel32LibDir(
486489 result_buf.shrinkAndFree(0);
487490 try result_buf.print("{s}\\Lib\\{s}\\um\\{s}", .{ install.path, install.version, arch_sub_dir });
488491
489 var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) {
492 var dir = Io.Dir.cwd().openDir(io, result_buf.items, .{}) catch |err| switch (err) {
490493 error.FileNotFound,
491494 error.NotDir,
492495 error.NoDevice,
......@@ -494,9 +497,9 @@ fn findNativeKernel32LibDir(
494497
495498 else => return error.FileSystem,
496499 };
497 defer dir.close();
500 defer dir.close(io);
498501
499 dir.access("kernel32.lib", .{}) catch |err| switch (err) {
502 dir.access(io, "kernel32.lib", .{}) catch |err| switch (err) {
500503 error.FileNotFound => continue,
501504 else => return error.FileSystem,
502505 };
......@@ -509,19 +512,18 @@ fn findNativeKernel32LibDir(
509512
510513fn findNativeMsvcIncludeDir(
511514 self: *LibCInstallation,
512 args: FindNativeOptions,
515 gpa: Allocator,
516 io: Io,
513517 sdk: std.zig.WindowsSdk,
514518) FindError!void {
515 const allocator = args.allocator;
516
517519 const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCStdLibHeaderNotFound;
518520 const up1 = fs.path.dirname(msvc_lib_dir) orelse return error.LibCStdLibHeaderNotFound;
519521 const up2 = fs.path.dirname(up1) orelse return error.LibCStdLibHeaderNotFound;
520522
521 const dir_path = try fs.path.join(allocator, &[_][]const u8{ up2, "include" });
522 errdefer allocator.free(dir_path);
523 const dir_path = try fs.path.join(gpa, &[_][]const u8{ up2, "include" });
524 errdefer gpa.free(dir_path);
523525
524 var dir = fs.cwd().openDir(dir_path, .{}) catch |err| switch (err) {
526 var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch |err| switch (err) {
525527 error.FileNotFound,
526528 error.NotDir,
527529 error.NoDevice,
......@@ -529,9 +531,9 @@ fn findNativeMsvcIncludeDir(
529531
530532 else => return error.FileSystem,
531533 };
532 defer dir.close();
534 defer dir.close(io);
533535
534 dir.access("vcruntime.h", .{}) catch |err| switch (err) {
536 dir.access(io, "vcruntime.h", .{}) catch |err| switch (err) {
535537 error.FileNotFound => return error.LibCStdLibHeaderNotFound,
536538 else => return error.FileSystem,
537539 };
......@@ -541,27 +543,23 @@ fn findNativeMsvcIncludeDir(
541543
542544fn findNativeMsvcLibDir(
543545 self: *LibCInstallation,
544 args: FindNativeOptions,
546 gpa: Allocator,
545547 sdk: std.zig.WindowsSdk,
546548) FindError!void {
547 const allocator = args.allocator;
548549 const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCRuntimeNotFound;
549 self.msvc_lib_dir = try allocator.dupe(u8, msvc_lib_dir);
550 self.msvc_lib_dir = try gpa.dupe(u8, msvc_lib_dir);
550551}
551552
552553pub const CCPrintFileNameOptions = struct {
553 allocator: Allocator,
554554 search_basename: []const u8,
555555 want_dirname: enum { full_path, only_dir },
556556 verbose: bool = false,
557557};
558558
559559/// caller owns returned memory
560fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
561 const allocator = args.allocator;
562
560fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 {
563561 // Detect infinite loops.
564 var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
562 var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) {
565563 error.Unexpected => unreachable, // WASI-only
566564 else => |e| return e,
567565 };
......@@ -578,17 +576,16 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
578576 break :blk false;
579577 };
580578
581 var argv = std.array_list.Managed([]const u8).init(allocator);
579 var argv = std.array_list.Managed([]const u8).init(gpa);
582580 defer argv.deinit();
583581
584 const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={s}", .{args.search_basename});
585 defer allocator.free(arg1);
582 const arg1 = try std.fmt.allocPrint(gpa, "-print-file-name={s}", .{args.search_basename});
583 defer gpa.free(arg1);
586584
587585 try appendCcExe(&argv, skip_cc_env_var);
588586 try argv.append(arg1);
589587
590 const run_res = std.process.Child.run(.{
591 .allocator = allocator,
588 const run_res = std.process.Child.run(gpa, io, .{
592589 .argv = argv.items,
593590 .max_output_bytes = 1024 * 1024,
594591 .env_map = &env_map,
......@@ -602,8 +599,8 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
602599 else => return error.UnableToSpawnCCompiler,
603600 };
604601 defer {
605 allocator.free(run_res.stdout);
606 allocator.free(run_res.stderr);
602 gpa.free(run_res.stdout);
603 gpa.free(run_res.stderr);
607604 }
608605 switch (run_res.term) {
609606 .Exited => |code| if (code != 0) {
......@@ -622,10 +619,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
622619 // So we detect failure by checking if the output matches exactly the input.
623620 if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound;
624621 switch (args.want_dirname) {
625 .full_path => return allocator.dupeZ(u8, line),
622 .full_path => return gpa.dupeZ(u8, line),
626623 .only_dir => {
627624 const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound;
628 return allocator.dupeZ(u8, dirname);
625 return gpa.dupeZ(u8, dirname);
629626 },
630627 }
631628}
......@@ -1015,17 +1012,3 @@ pub fn resolveCrtPaths(
10151012 },
10161013 }
10171014}
1018
1019const LibCInstallation = @This();
1020const std = @import("std");
1021const builtin = @import("builtin");
1022const Target = std.Target;
1023const fs = std.fs;
1024const Allocator = std.mem.Allocator;
1025const Path = std.Build.Cache.Path;
1026
1027const is_darwin = builtin.target.os.tag.isDarwin();
1028const is_windows = builtin.target.os.tag == .windows;
1029const is_haiku = builtin.target.os.tag == .haiku;
1030
1031const log = std.log.scoped(.libc_installation);
lib/std/zig/WindowsSdk.zig+158-147
......@@ -1,7 +1,11 @@
11const WindowsSdk = @This();
22const builtin = @import("builtin");
3
34const std = @import("std");
5const Io = std.Io;
6const Dir = std.Io.Dir;
47const Writer = std.Io.Writer;
8const Allocator = std.mem.Allocator;
59
610windows10sdk: ?Installation,
711windows81sdk: ?Installation,
......@@ -19,8 +23,8 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len
1923
2024/// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory.
2125/// Caller owns the result's fields.
22/// After finishing work, call `free(allocator)`.
23pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk {
26/// Returns memory allocated by `gpa`
27pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk {
2428 if (builtin.os.tag != .windows) return error.NotFound;
2529
2630 //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed
......@@ -29,27 +33,27 @@ pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutO
2933 };
3034 defer roots_key.closeKey();
3135
32 const windows10sdk = Installation.find(allocator, roots_key, "KitsRoot10", "", "v10.0") catch |err| switch (err) {
36 const windows10sdk = Installation.find(gpa, io, roots_key, "KitsRoot10", "", "v10.0") catch |err| switch (err) {
3337 error.InstallationNotFound => null,
3438 error.PathTooLong => null,
3539 error.VersionTooLong => null,
3640 error.OutOfMemory => return error.OutOfMemory,
3741 };
38 errdefer if (windows10sdk) |*w| w.free(allocator);
42 errdefer if (windows10sdk) |*w| w.free(gpa);
3943
40 const windows81sdk = Installation.find(allocator, roots_key, "KitsRoot81", "winver", "v8.1") catch |err| switch (err) {
44 const windows81sdk = Installation.find(gpa, io, roots_key, "KitsRoot81", "winver", "v8.1") catch |err| switch (err) {
4145 error.InstallationNotFound => null,
4246 error.PathTooLong => null,
4347 error.VersionTooLong => null,
4448 error.OutOfMemory => return error.OutOfMemory,
4549 };
46 errdefer if (windows81sdk) |*w| w.free(allocator);
50 errdefer if (windows81sdk) |*w| w.free(gpa);
4751
48 const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(allocator, arch) catch |err| switch (err) {
52 const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, arch) catch |err| switch (err) {
4953 error.MsvcLibDirNotFound => null,
5054 error.OutOfMemory => return error.OutOfMemory,
5155 };
52 errdefer allocator.free(msvc_lib_dir);
56 errdefer gpa.free(msvc_lib_dir);
5357
5458 return .{
5559 .windows10sdk = windows10sdk,
......@@ -58,15 +62,15 @@ pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutO
5862 };
5963}
6064
61pub fn free(sdk: WindowsSdk, allocator: std.mem.Allocator) void {
65pub fn free(sdk: WindowsSdk, gpa: Allocator) void {
6266 if (sdk.windows10sdk) |*w10sdk| {
63 w10sdk.free(allocator);
67 w10sdk.free(gpa);
6468 }
6569 if (sdk.windows81sdk) |*w81sdk| {
66 w81sdk.free(allocator);
70 w81sdk.free(gpa);
6771 }
6872 if (sdk.msvc_lib_dir) |msvc_lib_dir| {
69 allocator.free(msvc_lib_dir);
73 gpa.free(msvc_lib_dir);
7074 }
7175}
7276
......@@ -74,8 +78,9 @@ pub fn free(sdk: WindowsSdk, allocator: std.mem.Allocator) void {
7478/// and a version. Returns slice of version strings sorted in descending order.
7579/// Caller owns result.
7680fn iterateAndFilterByVersion(
77 iterator: *std.fs.Dir.Iterator,
78 allocator: std.mem.Allocator,
81 iterator: *Dir.Iterator,
82 gpa: Allocator,
83 io: Io,
7984 prefix: []const u8,
8085) error{OutOfMemory}![][]const u8 {
8186 const Version = struct {
......@@ -92,15 +97,15 @@ fn iterateAndFilterByVersion(
9297 std.mem.order(u8, lhs.build, rhs.build);
9398 }
9499 };
95 var versions = std.array_list.Managed(Version).init(allocator);
96 var dirs = std.array_list.Managed([]const u8).init(allocator);
100 var versions = std.array_list.Managed(Version).init(gpa);
101 var dirs = std.array_list.Managed([]const u8).init(gpa);
97102 defer {
98103 versions.deinit();
99 for (dirs.items) |filtered_dir| allocator.free(filtered_dir);
104 for (dirs.items) |filtered_dir| gpa.free(filtered_dir);
100105 dirs.deinit();
101106 }
102107
103 iterate: while (iterator.next() catch null) |entry| {
108 iterate: while (iterator.next(io) catch null) |entry| {
104109 if (entry.kind != .directory) continue;
105110 if (!std.mem.startsWith(u8, entry.name, prefix)) continue;
106111
......@@ -116,8 +121,8 @@ fn iterateAndFilterByVersion(
116121 num.* = Version.parseNum(num_it.next() orelse break) orelse continue :iterate
117122 else if (num_it.next()) |_| continue;
118123
119 const name = try allocator.dupe(u8, suffix);
120 errdefer allocator.free(name);
124 const name = try gpa.dupe(u8, suffix);
125 errdefer gpa.free(name);
121126 if (underscore) |pos| version.build = name[pos + 1 ..];
122127
123128 try versions.append(version);
......@@ -174,7 +179,7 @@ const RegistryWtf8 = struct {
174179
175180 /// Get string from registry.
176181 /// Caller owns result.
177 pub fn getString(reg: RegistryWtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
182 pub fn getString(reg: RegistryWtf8, gpa: Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
178183 const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
179184 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
180185 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
......@@ -190,11 +195,11 @@ const RegistryWtf8 = struct {
190195 };
191196
192197 const registry_wtf16le: RegistryWtf16Le = .{ .key = reg.key };
193 const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le);
194 defer allocator.free(value_wtf16le);
198 const value_wtf16le = try registry_wtf16le.getString(gpa, subkey_wtf16le, value_name_wtf16le);
199 defer gpa.free(value_wtf16le);
195200
196 const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le);
197 errdefer allocator.free(value_wtf8);
201 const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, value_wtf16le);
202 errdefer gpa.free(value_wtf8);
198203
199204 return value_wtf8;
200205 }
......@@ -282,7 +287,7 @@ const RegistryWtf16Le = struct {
282287 }
283288
284289 /// Get string ([:0]const u16) from registry.
285 fn getString(reg: RegistryWtf16Le, allocator: std.mem.Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {
290 fn getString(reg: RegistryWtf16Le, gpa: Allocator, subkey_wtf16le: [:0]const u16, value_name_wtf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {
286291 var actual_type: windows.ULONG = undefined;
287292
288293 // Calculating length to allocate
......@@ -311,8 +316,8 @@ const RegistryWtf16Le = struct {
311316 else => return error.NotAString,
312317 }
313318
314 const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);
315 errdefer allocator.free(value_wtf16le_buf);
319 const value_wtf16le_buf: []u16 = try gpa.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);
320 errdefer gpa.free(value_wtf16le_buf);
316321
317322 return_code_int = windows.advapi32.RegGetValueW(
318323 reg.key,
......@@ -346,7 +351,7 @@ const RegistryWtf16Le = struct {
346351 break :value_wtf16le std.mem.span(value_wtf16le_overestimated);
347352 };
348353
349 _ = allocator.resize(value_wtf16le_buf, value_wtf16le.len);
354 _ = gpa.resize(value_wtf16le_buf, value_wtf16le.len);
350355 return value_wtf16le;
351356 }
352357
......@@ -414,88 +419,89 @@ pub const Installation = struct {
414419
415420 /// Find path and version of Windows SDK.
416421 /// Caller owns the result's fields.
417 /// After finishing work, call `free(allocator)`.
418422 fn find(
419 allocator: std.mem.Allocator,
423 gpa: Allocator,
424 io: Io,
420425 roots_key: RegistryWtf8,
421426 roots_subkey: []const u8,
422427 prefix: []const u8,
423428 version_key_name: []const u8,
424429 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
425430 roots: {
426 const installation = findFromRoot(allocator, roots_key, roots_subkey, prefix) catch
431 const installation = findFromRoot(gpa, io, roots_key, roots_subkey, prefix) catch
427432 break :roots;
428433 if (installation.isValidVersion()) return installation;
429 installation.free(allocator);
434 installation.free(gpa);
430435 }
431436 {
432 const installation = try findFromInstallationFolder(allocator, version_key_name);
437 const installation = try findFromInstallationFolder(gpa, version_key_name);
433438 if (installation.isValidVersion()) return installation;
434 installation.free(allocator);
439 installation.free(gpa);
435440 }
436441 return error.InstallationNotFound;
437442 }
438443
439444 fn findFromRoot(
440 allocator: std.mem.Allocator,
445 gpa: Allocator,
446 io: Io,
441447 roots_key: RegistryWtf8,
442448 roots_subkey: []const u8,
443449 prefix: []const u8,
444450 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
445451 const path = path: {
446 const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", roots_subkey) catch |err| switch (err) {
452 const path_maybe_with_trailing_slash = roots_key.getString(gpa, "", roots_subkey) catch |err| switch (err) {
447453 error.NotAString => return error.InstallationNotFound,
448454 error.ValueNameNotFound => return error.InstallationNotFound,
449455 error.StringNotFound => return error.InstallationNotFound,
450456
451457 error.OutOfMemory => return error.OutOfMemory,
452458 };
453 if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) {
454 allocator.free(path_maybe_with_trailing_slash);
459 if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) {
460 gpa.free(path_maybe_with_trailing_slash);
455461 return error.PathTooLong;
456462 }
457463
458 var path = std.array_list.Managed(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash);
464 var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash);
459465 errdefer path.deinit();
460466
461467 // String might contain trailing slash, so trim it here
462468 if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop();
463469 break :path try path.toOwnedSlice();
464470 };
465 errdefer allocator.free(path);
471 errdefer gpa.free(path);
466472
467473 const version = version: {
468 var buf: [std.fs.max_path_bytes]u8 = undefined;
474 var buf: [Dir.max_path_bytes]u8 = undefined;
469475 const sdk_lib_dir_path = std.fmt.bufPrint(buf[0..], "{s}\\Lib\\", .{path}) catch |err| switch (err) {
470476 error.NoSpaceLeft => return error.PathTooLong,
471477 };
472 if (!std.fs.path.isAbsolute(sdk_lib_dir_path)) return error.InstallationNotFound;
478 if (!Dir.path.isAbsolute(sdk_lib_dir_path)) return error.InstallationNotFound;
473479
474480 // enumerate files in sdk path looking for latest version
475 var sdk_lib_dir = std.fs.openDirAbsolute(sdk_lib_dir_path, .{
481 var sdk_lib_dir = Dir.openDirAbsolute(io, sdk_lib_dir_path, .{
476482 .iterate = true,
477483 }) catch |err| switch (err) {
478484 error.NameTooLong => return error.PathTooLong,
479485 else => return error.InstallationNotFound,
480486 };
481 defer sdk_lib_dir.close();
487 defer sdk_lib_dir.close(io);
482488
483489 var iterator = sdk_lib_dir.iterate();
484 const versions = try iterateAndFilterByVersion(&iterator, allocator, prefix);
490 const versions = try iterateAndFilterByVersion(&iterator, gpa, io, prefix);
485491 if (versions.len == 0) return error.InstallationNotFound;
486492 defer {
487 for (versions[1..]) |version| allocator.free(version);
488 allocator.free(versions);
493 for (versions[1..]) |version| gpa.free(version);
494 gpa.free(versions);
489495 }
490496 break :version versions[0];
491497 };
492 errdefer allocator.free(version);
498 errdefer gpa.free(version);
493499
494500 return .{ .path = path, .version = version };
495501 }
496502
497503 fn findFromInstallationFolder(
498 allocator: std.mem.Allocator,
504 gpa: Allocator,
499505 version_key_name: []const u8,
500506 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
501507 var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined;
......@@ -514,7 +520,7 @@ pub const Installation = struct {
514520 defer key.closeKey();
515521
516522 const path: []const u8 = path: {
517 const path_maybe_with_trailing_slash = key.getString(allocator, "", "InstallationFolder") catch |err| switch (err) {
523 const path_maybe_with_trailing_slash = key.getString(gpa, "", "InstallationFolder") catch |err| switch (err) {
518524 error.NotAString => return error.InstallationNotFound,
519525 error.ValueNameNotFound => return error.InstallationNotFound,
520526 error.StringNotFound => return error.InstallationNotFound,
......@@ -522,12 +528,12 @@ pub const Installation = struct {
522528 error.OutOfMemory => return error.OutOfMemory,
523529 };
524530
525 if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) {
526 allocator.free(path_maybe_with_trailing_slash);
531 if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) {
532 gpa.free(path_maybe_with_trailing_slash);
527533 return error.PathTooLong;
528534 }
529535
530 var path = std.array_list.Managed(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash);
536 var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash);
531537 errdefer path.deinit();
532538
533539 // String might contain trailing slash, so trim it here
......@@ -536,12 +542,12 @@ pub const Installation = struct {
536542 const path_without_trailing_slash = try path.toOwnedSlice();
537543 break :path path_without_trailing_slash;
538544 };
539 errdefer allocator.free(path);
545 errdefer gpa.free(path);
540546
541547 const version: []const u8 = version: {
542548
543549 // note(dimenus): Microsoft doesn't include the .0 in the ProductVersion key....
544 const version_without_0 = key.getString(allocator, "", "ProductVersion") catch |err| switch (err) {
550 const version_without_0 = key.getString(gpa, "", "ProductVersion") catch |err| switch (err) {
545551 error.NotAString => return error.InstallationNotFound,
546552 error.ValueNameNotFound => return error.InstallationNotFound,
547553 error.StringNotFound => return error.InstallationNotFound,
......@@ -549,11 +555,11 @@ pub const Installation = struct {
549555 error.OutOfMemory => return error.OutOfMemory,
550556 };
551557 if (version_without_0.len + ".0".len > product_version_max_length) {
552 allocator.free(version_without_0);
558 gpa.free(version_without_0);
553559 return error.VersionTooLong;
554560 }
555561
556 var version = std.array_list.Managed(u8).fromOwnedSlice(allocator, version_without_0);
562 var version = std.array_list.Managed(u8).fromOwnedSlice(gpa, version_without_0);
557563 errdefer version.deinit();
558564
559565 try version.appendSlice(".0");
......@@ -561,14 +567,14 @@ pub const Installation = struct {
561567 const version_with_0 = try version.toOwnedSlice();
562568 break :version version_with_0;
563569 };
564 errdefer allocator.free(version);
570 errdefer gpa.free(version);
565571
566572 return .{ .path = path, .version = version };
567573 }
568574
569575 /// Check whether this version is enumerated in registry.
570576 fn isValidVersion(installation: Installation) bool {
571 var buf: [std.fs.max_path_bytes]u8 = undefined;
577 var buf: [Dir.max_path_bytes]u8 = undefined;
572578 const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{
573579 windows_kits_reg_key,
574580 installation.version,
......@@ -597,21 +603,21 @@ pub const Installation = struct {
597603 return (reg_value == 1);
598604 }
599605
600 fn free(install: Installation, allocator: std.mem.Allocator) void {
601 allocator.free(install.path);
602 allocator.free(install.version);
606 fn free(install: Installation, gpa: Allocator) void {
607 gpa.free(install.path);
608 gpa.free(install.version);
603609 }
604610};
605611
606612const MsvcLibDir = struct {
607 fn findInstancesDirViaSetup(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir {
613 fn findInstancesDirViaSetup(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir {
608614 const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup";
609615 const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) {
610616 error.KeyNotFound => return error.PathNotFound,
611617 };
612618 defer vs_setup_key.closeKey();
613619
614 const packages_path = vs_setup_key.getString(allocator, "", "CachePath") catch |err| switch (err) {
620 const packages_path = vs_setup_key.getString(gpa, "", "CachePath") catch |err| switch (err) {
615621 error.NotAString,
616622 error.ValueNameNotFound,
617623 error.StringNotFound,
......@@ -619,24 +625,24 @@ const MsvcLibDir = struct {
619625
620626 error.OutOfMemory => return error.OutOfMemory,
621627 };
622 defer allocator.free(packages_path);
628 defer gpa.free(packages_path);
623629
624 if (!std.fs.path.isAbsolute(packages_path)) return error.PathNotFound;
630 if (!Dir.path.isAbsolute(packages_path)) return error.PathNotFound;
625631
626 const instances_path = try std.fs.path.join(allocator, &.{ packages_path, "_Instances" });
627 defer allocator.free(instances_path);
632 const instances_path = try Dir.path.join(gpa, &.{ packages_path, "_Instances" });
633 defer gpa.free(instances_path);
628634
629 return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound;
635 return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound;
630636 }
631637
632 fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir {
638 fn findInstancesDirViaCLSID(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir {
633639 const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}";
634640 const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid, .{}) catch |err| switch (err) {
635641 error.KeyNotFound => return error.PathNotFound,
636642 };
637643 defer setup_config_key.closeKey();
638644
639 const dll_path = setup_config_key.getString(allocator, "InprocServer32", "") catch |err| switch (err) {
645 const dll_path = setup_config_key.getString(gpa, "InprocServer32", "") catch |err| switch (err) {
640646 error.NotAString,
641647 error.ValueNameNotFound,
642648 error.StringNotFound,
......@@ -644,11 +650,11 @@ const MsvcLibDir = struct {
644650
645651 error.OutOfMemory => return error.OutOfMemory,
646652 };
647 defer allocator.free(dll_path);
653 defer gpa.free(dll_path);
648654
649 if (!std.fs.path.isAbsolute(dll_path)) return error.PathNotFound;
655 if (!Dir.path.isAbsolute(dll_path)) return error.PathNotFound;
650656
651 var path_it = std.fs.path.componentIterator(dll_path);
657 var path_it = Dir.path.componentIterator(dll_path);
652658 // the .dll filename
653659 _ = path_it.last();
654660 const root_path = while (path_it.previous()) |dir_component| {
......@@ -659,17 +665,17 @@ const MsvcLibDir = struct {
659665 return error.PathNotFound;
660666 };
661667
662 const instances_path = try std.fs.path.join(allocator, &.{ root_path, "Packages", "_Instances" });
663 defer allocator.free(instances_path);
668 const instances_path = try Dir.path.join(gpa, &.{ root_path, "Packages", "_Instances" });
669 defer gpa.free(instances_path);
664670
665 return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound;
671 return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound;
666672 }
667673
668 fn findInstancesDir(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir {
674 fn findInstancesDir(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir {
669675 // First, try getting the packages cache path from the registry.
670676 // This only seems to exist when the path is different from the default.
671677 method1: {
672 return findInstancesDirViaSetup(allocator) catch |err| switch (err) {
678 return findInstancesDirViaSetup(gpa, io) catch |err| switch (err) {
673679 error.OutOfMemory => |e| return e,
674680 error.PathNotFound => break :method1,
675681 };
......@@ -677,7 +683,7 @@ const MsvcLibDir = struct {
677683 // Otherwise, try to get the path from the .dll that would have been
678684 // loaded via COM for SetupConfiguration.
679685 method2: {
680 return findInstancesDirViaCLSID(allocator) catch |err| switch (err) {
686 return findInstancesDirViaCLSID(gpa, io) catch |err| switch (err) {
681687 error.OutOfMemory => |e| return e,
682688 error.PathNotFound => break :method2,
683689 };
......@@ -685,19 +691,19 @@ const MsvcLibDir = struct {
685691 // If that can't be found, fall back to manually appending
686692 // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA%
687693 method3: {
688 const program_data = std.process.getEnvVarOwned(allocator, "PROGRAMDATA") catch |err| switch (err) {
694 const program_data = std.process.getEnvVarOwned(gpa, "PROGRAMDATA") catch |err| switch (err) {
689695 error.OutOfMemory => |e| return e,
690696 error.InvalidWtf8 => unreachable,
691697 error.EnvironmentVariableNotFound => break :method3,
692698 };
693 defer allocator.free(program_data);
699 defer gpa.free(program_data);
694700
695 if (!std.fs.path.isAbsolute(program_data)) break :method3;
701 if (!Dir.path.isAbsolute(program_data)) break :method3;
696702
697 const instances_path = try std.fs.path.join(allocator, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" });
698 defer allocator.free(instances_path);
703 const instances_path = try Dir.path.join(gpa, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" });
704 defer gpa.free(instances_path);
699705
700 return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch break :method3;
706 return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch break :method3;
701707 }
702708 return error.PathNotFound;
703709 }
......@@ -748,33 +754,33 @@ const MsvcLibDir = struct {
748754 ///
749755 /// The logic in this function is intended to match what ISetupConfiguration does
750756 /// under-the-hood, as verified using Procmon.
751 fn findViaCOM(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {
757 fn findViaCOM(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {
752758 // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances`
753759 // This will contain directories with names of instance IDs like 80a758ca,
754760 // which will contain `state.json` files that have the version and
755761 // installation directory.
756 var instances_dir = try findInstancesDir(allocator);
757 defer instances_dir.close();
762 var instances_dir = try findInstancesDir(gpa, io);
763 defer instances_dir.close(io);
758764
759 var state_subpath_buf: [std.fs.max_name_bytes + 32]u8 = undefined;
765 var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined;
760766 var latest_version_lib_dir: std.ArrayList(u8) = .empty;
761 errdefer latest_version_lib_dir.deinit(allocator);
767 errdefer latest_version_lib_dir.deinit(gpa);
762768
763769 var latest_version: u64 = 0;
764770 var instances_dir_it = instances_dir.iterateAssumeFirstIteration();
765 while (instances_dir_it.next() catch return error.PathNotFound) |entry| {
771 while (instances_dir_it.next(io) catch return error.PathNotFound) |entry| {
766772 if (entry.kind != .directory) continue;
767773
768774 var writer: Writer = .fixed(&state_subpath_buf);
769775
770776 writer.writeAll(entry.name) catch unreachable;
771 writer.writeByte(std.fs.path.sep) catch unreachable;
777 writer.writeByte(Dir.path.sep) catch unreachable;
772778 writer.writeAll("state.json") catch unreachable;
773779
774 const json_contents = instances_dir.readFileAlloc(writer.buffered(), allocator, .limited(std.math.maxInt(usize))) catch continue;
775 defer allocator.free(json_contents);
780 const json_contents = instances_dir.readFileAlloc(io, writer.buffered(), gpa, .limited(std.math.maxInt(usize))) catch continue;
781 defer gpa.free(json_contents);
776782
777 var parsed = std.json.parseFromSlice(std.json.Value, allocator, json_contents, .{}) catch continue;
783 var parsed = std.json.parseFromSlice(std.json.Value, gpa, json_contents, .{}) catch continue;
778784 defer parsed.deinit();
779785
780786 if (parsed.value != .object) continue;
......@@ -791,35 +797,40 @@ const MsvcLibDir = struct {
791797 const installation_path = parsed.value.object.get("installationPath") orelse continue;
792798 if (installation_path != .string) continue;
793799
794 const lib_dir_path = libDirFromInstallationPath(allocator, installation_path.string, arch) catch |err| switch (err) {
800 const lib_dir_path = libDirFromInstallationPath(gpa, io, installation_path.string, arch) catch |err| switch (err) {
795801 error.OutOfMemory => |e| return e,
796802 error.PathNotFound => continue,
797803 };
798 defer allocator.free(lib_dir_path);
804 defer gpa.free(lib_dir_path);
799805
800806 latest_version_lib_dir.clearRetainingCapacity();
801 try latest_version_lib_dir.appendSlice(allocator, lib_dir_path);
807 try latest_version_lib_dir.appendSlice(gpa, lib_dir_path);
802808 latest_version = parsed_version;
803809 }
804810
805811 if (latest_version_lib_dir.items.len == 0) return error.PathNotFound;
806 return latest_version_lib_dir.toOwnedSlice(allocator);
812 return latest_version_lib_dir.toOwnedSlice(gpa);
807813 }
808814
809 fn libDirFromInstallationPath(allocator: std.mem.Allocator, installation_path: []const u8, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {
810 var lib_dir_buf = try std.array_list.Managed(u8).initCapacity(allocator, installation_path.len + 64);
815 fn libDirFromInstallationPath(
816 gpa: Allocator,
817 io: Io,
818 installation_path: []const u8,
819 arch: std.Target.Cpu.Arch,
820 ) error{ OutOfMemory, PathNotFound }![]const u8 {
821 var lib_dir_buf = try std.array_list.Managed(u8).initCapacity(gpa, installation_path.len + 64);
811822 errdefer lib_dir_buf.deinit();
812823
813824 lib_dir_buf.appendSliceAssumeCapacity(installation_path);
814825
815 if (!std.fs.path.isSep(lib_dir_buf.getLast())) {
826 if (!Dir.path.isSep(lib_dir_buf.getLast())) {
816827 try lib_dir_buf.append('\\');
817828 }
818829 const installation_path_with_trailing_sep_len = lib_dir_buf.items.len;
819830
820831 try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt");
821832 var default_tools_version_buf: [512]u8 = undefined;
822 const default_tools_version_contents = std.fs.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch {
833 const default_tools_version_contents = Dir.cwd().readFile(io, lib_dir_buf.items, &default_tools_version_buf) catch {
823834 return error.PathNotFound;
824835 };
825836 var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n");
......@@ -837,7 +848,7 @@ const MsvcLibDir = struct {
837848 else => unreachable,
838849 });
839850
840 if (!verifyLibDir(lib_dir_buf.items)) {
851 if (!verifyLibDir(io, lib_dir_buf.items)) {
841852 return error.PathNotFound;
842853 }
843854
......@@ -845,64 +856,64 @@ const MsvcLibDir = struct {
845856 }
846857
847858 // https://learn.microsoft.com/en-us/visualstudio/install/tools-for-managing-visual-studio-instances?view=vs-2022#editing-the-registry-for-a-visual-studio-instance
848 fn findViaRegistry(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {
859 fn findViaRegistry(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {
849860
850861 // %localappdata%\Microsoft\VisualStudio\
851862 // %appdata%\Local\Microsoft\VisualStudio\
852 const visualstudio_folder_path = std.fs.getAppDataDir(allocator, "Microsoft\\VisualStudio\\") catch return error.PathNotFound;
853 defer allocator.free(visualstudio_folder_path);
863 const visualstudio_folder_path = std.fs.getAppDataDir(gpa, "Microsoft\\VisualStudio\\") catch return error.PathNotFound;
864 defer gpa.free(visualstudio_folder_path);
854865
855866 const vs_versions: []const []const u8 = vs_versions: {
856 if (!std.fs.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound;
867 if (!Dir.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound;
857868 // enumerate folders that contain `privateregistry.bin`, looking for all versions
858869 // f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\
859 var visualstudio_folder = std.fs.openDirAbsolute(visualstudio_folder_path, .{
870 var visualstudio_folder = Dir.openDirAbsolute(io, visualstudio_folder_path, .{
860871 .iterate = true,
861872 }) catch return error.PathNotFound;
862 defer visualstudio_folder.close();
873 defer visualstudio_folder.close(io);
863874
864875 var iterator = visualstudio_folder.iterate();
865 break :vs_versions try iterateAndFilterByVersion(&iterator, allocator, "");
876 break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, io, "");
866877 };
867878 defer {
868 for (vs_versions) |vs_version| allocator.free(vs_version);
869 allocator.free(vs_versions);
879 for (vs_versions) |vs_version| gpa.free(vs_version);
880 gpa.free(vs_versions);
870881 }
871882 var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined;
872883 const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {
873 const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
874 defer allocator.free(privateregistry_absolute_path);
875 if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue;
884 const privateregistry_absolute_path = Dir.path.join(gpa, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
885 defer gpa.free(privateregistry_absolute_path);
886 if (!Dir.path.isAbsolute(privateregistry_absolute_path)) continue;
876887
877888 const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue;
878889 defer visualstudio_registry.closeKey();
879890
880891 const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable;
881892
882 const source_directories_value = visualstudio_registry.getString(allocator, config_subkey, "Source Directories") catch |err| switch (err) {
893 const source_directories_value = visualstudio_registry.getString(gpa, config_subkey, "Source Directories") catch |err| switch (err) {
883894 error.OutOfMemory => return error.OutOfMemory,
884895 else => continue,
885896 };
886 if (source_directories_value.len > (std.fs.max_path_bytes * 30)) { // note(bratishkaerik): guessing from the fact that on my computer it has 15 paths and at least some of them are not of max length
887 allocator.free(source_directories_value);
897 if (source_directories_value.len > (Dir.max_path_bytes * 30)) { // note(bratishkaerik): guessing from the fact that on my computer it has 15 paths and at least some of them are not of max length
898 gpa.free(source_directories_value);
888899 continue;
889900 }
890901
891902 break :source_directories source_directories_value;
892903 } else return error.PathNotFound;
893 defer allocator.free(source_directories);
904 defer gpa.free(source_directories);
894905
895906 var source_directories_split = std.mem.splitScalar(u8, source_directories, ';');
896907
897908 const msvc_dir: []const u8 = msvc_dir: {
898 const msvc_include_dir_maybe_with_trailing_slash = try allocator.dupe(u8, source_directories_split.first());
909 const msvc_include_dir_maybe_with_trailing_slash = try gpa.dupe(u8, source_directories_split.first());
899910
900 if (msvc_include_dir_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(msvc_include_dir_maybe_with_trailing_slash)) {
901 allocator.free(msvc_include_dir_maybe_with_trailing_slash);
911 if (msvc_include_dir_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(msvc_include_dir_maybe_with_trailing_slash)) {
912 gpa.free(msvc_include_dir_maybe_with_trailing_slash);
902913 return error.PathNotFound;
903914 }
904915
905 var msvc_dir = std.array_list.Managed(u8).fromOwnedSlice(allocator, msvc_include_dir_maybe_with_trailing_slash);
916 var msvc_dir = std.array_list.Managed(u8).fromOwnedSlice(gpa, msvc_include_dir_maybe_with_trailing_slash);
906917 errdefer msvc_dir.deinit();
907918
908919 // String might contain trailing slash, so trim it here
......@@ -924,19 +935,19 @@ const MsvcLibDir = struct {
924935 const msvc_dir_with_arch = try msvc_dir.toOwnedSlice();
925936 break :msvc_dir msvc_dir_with_arch;
926937 };
927 errdefer allocator.free(msvc_dir);
938 errdefer gpa.free(msvc_dir);
928939
929 if (!verifyLibDir(msvc_dir)) {
940 if (!verifyLibDir(io, msvc_dir)) {
930941 return error.PathNotFound;
931942 }
932943
933944 return msvc_dir;
934945 }
935946
936 fn findViaVs7Key(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {
947 fn findViaVs7Key(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {
937948 var base_path: std.array_list.Managed(u8) = base_path: {
938949 try_env: {
939 var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
950 var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) {
940951 error.OutOfMemory => return error.OutOfMemory,
941952 else => break :try_env,
942953 };
......@@ -944,8 +955,8 @@ const MsvcLibDir = struct {
944955
945956 if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| {
946957 if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env;
947 if (!std.fs.path.isAbsolute(VS140COMNTOOLS)) break :try_env;
948 var list = std.array_list.Managed(u8).init(allocator);
958 if (!Dir.path.isAbsolute(VS140COMNTOOLS)) break :try_env;
959 var list = std.array_list.Managed(u8).init(gpa);
949960 errdefer list.deinit();
950961
951962 try list.appendSlice(VS140COMNTOOLS); // C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools
......@@ -959,17 +970,17 @@ const MsvcLibDir = struct {
959970 const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", .{ .wow64_32 = true }) catch return error.PathNotFound;
960971 defer vs7_key.closeKey();
961972 try_vs7_key: {
962 const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) {
973 const path_maybe_with_trailing_slash = vs7_key.getString(gpa, "", "14.0") catch |err| switch (err) {
963974 error.OutOfMemory => return error.OutOfMemory,
964975 else => break :try_vs7_key,
965976 };
966977
967 if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) {
968 allocator.free(path_maybe_with_trailing_slash);
978 if (path_maybe_with_trailing_slash.len > Dir.max_path_bytes or !Dir.path.isAbsolute(path_maybe_with_trailing_slash)) {
979 gpa.free(path_maybe_with_trailing_slash);
969980 break :try_vs7_key;
970981 }
971982
972 var path = std.array_list.Managed(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash);
983 var path = std.array_list.Managed(u8).fromOwnedSlice(gpa, path_maybe_with_trailing_slash);
973984 errdefer path.deinit();
974985
975986 // String might contain trailing slash, so trim it here
......@@ -989,7 +1000,7 @@ const MsvcLibDir = struct {
9891000 else => unreachable,
9901001 });
9911002
992 if (!verifyLibDir(base_path.items)) {
1003 if (!verifyLibDir(io, base_path.items)) {
9931004 return error.PathNotFound;
9941005 }
9951006
......@@ -997,13 +1008,13 @@ const MsvcLibDir = struct {
9971008 return full_path;
9981009 }
9991010
1000 fn verifyLibDir(lib_dir_path: []const u8) bool {
1001 std.debug.assert(std.fs.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*`
1011 fn verifyLibDir(io: Io, lib_dir_path: []const u8) bool {
1012 std.debug.assert(Dir.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*`
10021013
1003 var dir = std.fs.openDirAbsolute(lib_dir_path, .{}) catch return false;
1004 defer dir.close();
1014 var dir = Dir.openDirAbsolute(io, lib_dir_path, .{}) catch return false;
1015 defer dir.close(io);
10051016
1006 const stat = dir.statFile("vcruntime.lib") catch return false;
1017 const stat = dir.statFile(io, "vcruntime.lib", .{}) catch return false;
10071018 if (stat.kind != .file)
10081019 return false;
10091020
......@@ -1012,18 +1023,18 @@ const MsvcLibDir = struct {
10121023
10131024 /// Find path to MSVC's `lib/` directory.
10141025 /// Caller owns the result.
1015 pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 {
1016 const full_path = MsvcLibDir.findViaCOM(allocator, arch) catch |err1| switch (err1) {
1026 pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 {
1027 const full_path = MsvcLibDir.findViaCOM(gpa, io, arch) catch |err1| switch (err1) {
10171028 error.OutOfMemory => return error.OutOfMemory,
1018 error.PathNotFound => MsvcLibDir.findViaRegistry(allocator, arch) catch |err2| switch (err2) {
1029 error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch) catch |err2| switch (err2) {
10191030 error.OutOfMemory => return error.OutOfMemory,
1020 error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator, arch) catch |err3| switch (err3) {
1031 error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, arch) catch |err3| switch (err3) {
10211032 error.OutOfMemory => return error.OutOfMemory,
10221033 error.PathNotFound => return error.MsvcLibDirNotFound,
10231034 },
10241035 },
10251036 };
1026 errdefer allocator.free(full_path);
1037 errdefer gpa.free(full_path);
10271038
10281039 return full_path;
10291040 }
lib/std/zig/Zir.zig+4-4
......@@ -11,9 +11,11 @@
1111//! * In the future, possibly inline assembly, which needs to get parsed and
1212//! handled by the codegen backend, and errors reported there. However for now,
1313//! inline assembly is not an exception.
14const Zir = @This();
15const builtin = @import("builtin");
1416
1517const std = @import("std");
16const builtin = @import("builtin");
18const Io = std.Io;
1719const mem = std.mem;
1820const Allocator = std.mem.Allocator;
1921const assert = std.debug.assert;
......@@ -21,8 +23,6 @@ const BigIntConst = std.math.big.int.Const;
2123const BigIntMutable = std.math.big.int.Mutable;
2224const Ast = std.zig.Ast;
2325
24const Zir = @This();
25
2626instructions: std.MultiArrayList(Inst).Slice,
2727/// In order to store references to strings in fewer bytes, we copy all
2828/// string bytes into here. String bytes can be null. It is up to whomever
......@@ -45,7 +45,7 @@ pub const Header = extern struct {
4545 /// it's essentially free to have a zero field here and makes the warning go away,
4646 /// making it more likely that following Valgrind warnings will be taken seriously.
4747 unused: u32 = 0,
48 stat_inode: std.fs.File.INode,
48 stat_inode: Io.File.INode,
4949 stat_size: u64,
5050 stat_mtime: i128,
5151};
lib/std/zig/Zoir.zig+8-7
......@@ -1,6 +1,13 @@
11//! Zig Object Intermediate Representation.
22//! Simplified AST for the ZON (Zig Object Notation) format.
33//! `ZonGen` converts `Ast` to `Zoir`.
4const Zoir = @This();
5
6const std = @import("std");
7const Io = std.Io;
8const assert = std.debug.assert;
9const Allocator = std.mem.Allocator;
10const Ast = std.zig.Ast;
411
512nodes: std.MultiArrayList(Node.Repr).Slice,
613extra: []u32,
......@@ -25,7 +32,7 @@ pub const Header = extern struct {
2532 /// making it more likely that following Valgrind warnings will be taken seriously.
2633 unused: u64 = 0,
2734
28 stat_inode: std.fs.File.INode,
35 stat_inode: Io.File.INode,
2936 stat_size: u64,
3037 stat_mtime: i128,
3138
......@@ -254,9 +261,3 @@ pub const CompileError = extern struct {
254261 assert(std.meta.hasUniqueRepresentation(Note));
255262 }
256263};
257
258const std = @import("std");
259const assert = std.debug.assert;
260const Allocator = std.mem.Allocator;
261const Ast = std.zig.Ast;
262const Zoir = @This();
lib/std/zig/llvm/Builder.zig+16-13
......@@ -1,14 +1,17 @@
1const builtin = @import("builtin");
2const Builder = @This();
3
14const std = @import("../../std.zig");
5const Io = std.Io;
26const Allocator = std.mem.Allocator;
37const assert = std.debug.assert;
4const bitcode_writer = @import("bitcode_writer.zig");
5const Builder = @This();
6const builtin = @import("builtin");
78const DW = std.dwarf;
8const ir = @import("ir.zig");
99const log = std.log.scoped(.llvm);
1010const Writer = std.Io.Writer;
1111
12const bitcode_writer = @import("bitcode_writer.zig");
13const ir = @import("ir.zig");
14
1215gpa: Allocator,
1316strip: bool,
1417
......@@ -9573,21 +9576,21 @@ pub fn asmValue(
95739576 return (try self.asmConst(ty, info, assembly, constraints)).toValue();
95749577}
95759578
9576pub fn dump(b: *Builder) void {
9579pub fn dump(b: *Builder, io: Io) void {
95779580 var buffer: [4000]u8 = undefined;
9578 const stderr: std.fs.File = .stderr();
9579 b.printToFile(stderr, &buffer) catch {};
9581 const stderr: Io.File = .stderr();
9582 b.printToFile(io, stderr, &buffer) catch {};
95809583}
95819584
9582pub fn printToFilePath(b: *Builder, dir: std.fs.Dir, path: []const u8) !void {
9585pub fn printToFilePath(b: *Builder, io: Io, dir: Io.Dir, path: []const u8) !void {
95839586 var buffer: [4000]u8 = undefined;
9584 const file = try dir.createFile(path, .{});
9585 defer file.close();
9586 try b.printToFile(file, &buffer);
9587 const file = try dir.createFile(io, path, .{});
9588 defer file.close(io);
9589 try b.printToFile(io, file, &buffer);
95879590}
95889591
9589pub fn printToFile(b: *Builder, file: std.fs.File, buffer: []u8) !void {
9590 var fw = file.writer(buffer);
9592pub fn printToFile(b: *Builder, io: Io, file: Io.File, buffer: []u8) !void {
9593 var fw = file.writer(io, buffer);
95919594 try print(b, &fw.interface);
95929595 try fw.interface.flush();
95939596}
lib/std/zig/parser_test.zig+27-18
......@@ -1,7 +1,6 @@
11const std = @import("std");
2const mem = std.mem;
3const print = std.debug.print;
4const maxInt = std.math.maxInt;
2const Io = std.Io;
3const Allocator = std.mem.Allocator;
54
65test "zig fmt: remove extra whitespace at start and end of file with comment between" {
76 try testTransform(
......@@ -4539,7 +4538,7 @@ test "zig fmt: Only indent multiline string literals in function calls" {
45394538test "zig fmt: Don't add extra newline after if" {
45404539 try testCanonical(
45414540 \\pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
4542 \\ if (cwd().symLink(existing_path, new_path, .{})) {
4541 \\ if (foo().bar(existing_path, new_path, .{})) {
45434542 \\ return;
45444543 \\ }
45454544 \\}
......@@ -6332,54 +6331,64 @@ test "ampersand" {
63326331
63336332var fixed_buffer_mem: [100 * 1024]u8 = undefined;
63346333
6335fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 {
6334fn testParse(io: Io, source: [:0]const u8, allocator: Allocator, anything_changed: *bool) ![]u8 {
63366335 var buffer: [64]u8 = undefined;
6337 const stderr, _ = std.debug.lockStderrWriter(&buffer);
6338 defer std.debug.unlockStderrWriter();
6336 const stderr = try io.lockStderr(&buffer, null);
6337 defer io.unlockStderr();
6338 const writer = &stderr.file_writer.interface;
63396339
63406340 var tree = try std.zig.Ast.parse(allocator, source, .zig);
63416341 defer tree.deinit(allocator);
63426342
63436343 for (tree.errors) |parse_error| {
63446344 const loc = tree.tokenLocation(0, parse_error.token);
6345 try stderr.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 });
6346 try tree.renderError(parse_error, stderr);
6347 try stderr.print("\n{s}\n", .{source[loc.line_start..loc.line_end]});
6345 try writer.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 });
6346 try tree.renderError(parse_error, writer);
6347 try writer.print("\n{s}\n", .{source[loc.line_start..loc.line_end]});
63486348 {
63496349 var i: usize = 0;
63506350 while (i < loc.column) : (i += 1) {
6351 try stderr.writeAll(" ");
6351 try writer.writeAll(" ");
63526352 }
6353 try stderr.writeAll("^");
6353 try writer.writeAll("^");
63546354 }
6355 try stderr.writeAll("\n");
6355 try writer.writeAll("\n");
63566356 }
63576357 if (tree.errors.len != 0) {
63586358 return error.ParseError;
63596359 }
63606360
63616361 const formatted = try tree.renderAlloc(allocator);
6362 anything_changed.* = !mem.eql(u8, formatted, source);
6362 anything_changed.* = !std.mem.eql(u8, formatted, source);
63636363 return formatted;
63646364}
6365fn testTransformImpl(allocator: mem.Allocator, fba: *std.heap.FixedBufferAllocator, source: [:0]const u8, expected_source: []const u8) !void {
6365fn testTransformImpl(
6366 allocator: Allocator,
6367 fba: *std.heap.FixedBufferAllocator,
6368 io: Io,
6369 source: [:0]const u8,
6370 expected_source: []const u8,
6371) !void {
63666372 // reset the fixed buffer allocator each run so that it can be re-used for each
63676373 // iteration of the failing index
63686374 fba.reset();
63696375 var anything_changed: bool = undefined;
6370 const result_source = try testParse(source, allocator, &anything_changed);
6376 const result_source = try testParse(io, source, allocator, &anything_changed);
63716377 try std.testing.expectEqualStrings(expected_source, result_source);
63726378 const changes_expected = source.ptr != expected_source.ptr;
63736379 if (anything_changed != changes_expected) {
6374 print("std.zig.render returned {} instead of {}\n", .{ anything_changed, changes_expected });
6380 std.debug.print("std.zig.render returned {} instead of {}\n", .{ anything_changed, changes_expected });
63756381 return error.TestFailed;
63766382 }
63776383 try std.testing.expect(anything_changed == changes_expected);
63786384 allocator.free(result_source);
63796385}
63806386fn testTransform(source: [:0]const u8, expected_source: []const u8) !void {
6387 const io = std.testing.io;
63816388 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
6382 return std.testing.checkAllAllocationFailures(fixed_allocator.allocator(), testTransformImpl, .{ &fixed_allocator, source, expected_source });
6389 return std.testing.checkAllAllocationFailures(fixed_allocator.allocator(), testTransformImpl, .{
6390 &fixed_allocator, io, source, expected_source,
6391 });
63836392}
63846393fn testCanonical(source: [:0]const u8) !void {
63856394 return testTransform(source, source);
lib/std/zig/perf_test.zig+2-1
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const mem = std.mem;
34const Tokenizer = std.zig.Tokenizer;
45const fmtIntSizeBin = std.fmt.fmtIntSizeBin;
......@@ -22,7 +23,7 @@ pub fn main() !void {
2223 const bytes_per_sec = @as(u64, @intFromFloat(@floor(bytes_per_sec_float)));
2324
2425 var stdout_buffer: [1024]u8 = undefined;
25 var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
26 var stdout_writer = Io.File.stdout().writer(&stdout_buffer);
2627 const stdout = &stdout_writer.interface;
2728 try stdout.print("parsing speed: {Bi:.2}/s, {Bi:.2} used \n", .{ bytes_per_sec, memory_used });
2829 try stdout.flush();
lib/std/zig/system.zig+23-21
......@@ -1,11 +1,12 @@
11const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
24const std = @import("../std.zig");
35const mem = std.mem;
46const elf = std.elf;
57const fs = std.fs;
68const assert = std.debug.assert;
79const Target = std.Target;
8const native_endian = builtin.cpu.arch.endian();
910const posix = std.posix;
1011const Io = std.Io;
1112
......@@ -39,6 +40,7 @@ pub const GetExternalExecutorOptions = struct {
3940/// Return whether or not the given host is capable of running executables of
4041/// the other target.
4142pub fn getExternalExecutor(
43 io: Io,
4244 host: *const std.Target,
4345 candidate: *const std.Target,
4446 options: GetExternalExecutorOptions,
......@@ -69,7 +71,7 @@ pub fn getExternalExecutor(
6971 if (os_match and cpu_ok) native: {
7072 if (options.link_libc) {
7173 if (candidate.dynamic_linker.get()) |candidate_dl| {
72 fs.cwd().access(candidate_dl, .{}) catch {
74 Io.Dir.cwd().access(io, candidate_dl, .{}) catch {
7375 bad_result = .{ .bad_dl = candidate_dl };
7476 break :native;
7577 };
......@@ -209,7 +211,6 @@ pub const DetectError = error{
209211 DeviceBusy,
210212 OSVersionDetectionFail,
211213 Unexpected,
212 ProcessNotFound,
213214} || Io.Cancelable;
214215
215216/// Given a `Target.Query`, which specifies in detail which parts of the
......@@ -247,7 +248,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
247248 os.version_range.windows.min = detected_version;
248249 os.version_range.windows.max = detected_version;
249250 },
250 .macos => try darwin.macos.detect(&os),
251 .macos => try darwin.macos.detect(io, &os),
251252 .freebsd, .netbsd, .dragonfly => {
252253 const key = switch (builtin.target.os.tag) {
253254 .freebsd => "kern.osreldate",
......@@ -322,7 +323,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
322323 error.Unexpected => return error.OSVersionDetectionFail,
323324 };
324325
325 if (Target.Query.parseVersion(buf[0..len :0])) |ver| {
326 if (Target.Query.parseVersion(buf[0 .. len - 1 :0])) |ver| {
326327 assert(ver.build == null);
327328 assert(ver.pre == null);
328329 os.version_range.semver.min = ver;
......@@ -422,7 +423,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
422423 error.SocketUnconnected => return error.Unexpected,
423424
424425 error.AccessDenied,
425 error.ProcessNotFound,
426426 error.SymLinkLoop,
427427 error.ProcessFdQuotaExceeded,
428428 error.SystemFdQuotaExceeded,
......@@ -553,7 +553,6 @@ pub const AbiAndDynamicLinkerFromFileError = error{
553553 SystemResources,
554554 ProcessFdQuotaExceeded,
555555 SystemFdQuotaExceeded,
556 ProcessNotFound,
557556 IsDir,
558557 WouldBlock,
559558 InputOutput,
......@@ -693,8 +692,10 @@ fn abiAndDynamicLinkerFromFile(
693692
694693 // So far, no luck. Next we try to see if the information is
695694 // present in the symlink data for the dynamic linker path.
696 var link_buf: [posix.PATH_MAX]u8 = undefined;
697 const link_name = posix.readlink(dl_path, &link_buf) catch |err| switch (err) {
695 var link_buffer: [posix.PATH_MAX]u8 = undefined;
696 const link_name = if (Io.Dir.readLinkAbsolute(io, dl_path, &link_buffer)) |n|
697 link_buffer[0..n]
698 else |err| switch (err) {
698699 error.NameTooLong => unreachable,
699700 error.BadPathName => unreachable, // Windows only
700701 error.UnsupportedReparsePointType => unreachable, // Windows only
......@@ -711,6 +712,7 @@ fn abiAndDynamicLinkerFromFile(
711712 error.SystemResources,
712713 error.FileSystem,
713714 error.SymLinkLoop,
715 error.Canceled,
714716 error.Unexpected,
715717 => |e| return e,
716718 };
......@@ -786,7 +788,9 @@ test glibcVerFromLinkName {
786788}
787789
788790fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
789 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
791 const cwd: Io.Dir = .cwd();
792
793 var dir = cwd.openDir(io, rpath, .{}) catch |err| switch (err) {
790794 error.NameTooLong => return error.Unexpected,
791795 error.BadPathName => return error.Unexpected,
792796 error.DeviceBusy => return error.Unexpected,
......@@ -805,7 +809,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
805809 error.Unexpected => |e| return e,
806810 error.Canceled => |e| return e,
807811 };
808 defer dir.close();
812 defer dir.close(io);
809813
810814 // Now we have a candidate for the path to libc shared object. In
811815 // the past, we used readlink() here because the link name would
......@@ -815,14 +819,14 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
815819 // .dynstr section, and finding the max version number of symbols
816820 // that start with "GLIBC_2.".
817821 const glibc_so_basename = "libc.so.6";
818 var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
822 var file = dir.openFile(io, glibc_so_basename, .{}) catch |err| switch (err) {
819823 error.NameTooLong => return error.Unexpected,
820824 error.BadPathName => return error.Unexpected,
821825 error.PipeBusy => return error.Unexpected, // Windows-only
822826 error.SharingViolation => return error.Unexpected, // Windows-only
823827 error.NetworkNotFound => return error.Unexpected, // Windows-only
824828 error.AntivirusInterference => return error.Unexpected, // Windows-only
825 error.FileLocksNotSupported => return error.Unexpected, // No lock requested.
829 error.FileLocksUnsupported => return error.Unexpected, // No lock requested.
826830 error.NoSpaceLeft => return error.Unexpected, // read-only
827831 error.PathAlreadyExists => return error.Unexpected, // read-only
828832 error.DeviceBusy => return error.Unexpected, // read-only
......@@ -837,7 +841,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
837841 error.NotDir => return error.GLibCNotFound,
838842 error.IsDir => return error.GLibCNotFound,
839843
840 error.ProcessNotFound => |e| return e,
841844 error.ProcessFdQuotaExceeded => |e| return e,
842845 error.SystemFdQuotaExceeded => |e| return e,
843846 error.SystemResources => |e| return e,
......@@ -845,11 +848,11 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
845848 error.Unexpected => |e| return e,
846849 error.Canceled => |e| return e,
847850 };
848 defer file.close();
851 defer file.close(io);
849852
850853 // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
851854 var buffer: [8000]u8 = undefined;
852 var file_reader: Io.File.Reader = .initAdapted(file, io, &buffer);
855 var file_reader: Io.File.Reader = .init(file, io, &buffer);
853856
854857 return glibcVerFromSoFile(&file_reader) catch |err| switch (err) {
855858 error.InvalidElfMagic,
......@@ -1024,14 +1027,14 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
10241027 };
10251028
10261029 while (true) {
1027 const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
1030 const file = Io.Dir.openFileAbsolute(io, file_name, .{}) catch |err| switch (err) {
10281031 error.NoSpaceLeft => return error.Unexpected,
10291032 error.NameTooLong => return error.Unexpected,
10301033 error.PathAlreadyExists => return error.Unexpected,
10311034 error.SharingViolation => return error.Unexpected,
10321035 error.BadPathName => return error.Unexpected,
10331036 error.PipeBusy => return error.Unexpected,
1034 error.FileLocksNotSupported => return error.Unexpected,
1037 error.FileLocksUnsupported => return error.Unexpected,
10351038 error.FileBusy => return error.Unexpected, // opened without write permissions
10361039 error.AntivirusInterference => return error.Unexpected, // Windows-only error
10371040
......@@ -1049,9 +1052,9 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
10491052 else => |e| return e,
10501053 };
10511054 var is_elf_file = false;
1052 defer if (!is_elf_file) file.close();
1055 defer if (!is_elf_file) file.close(io);
10531056
1054 file_reader = .initAdapted(file, io, &file_reader_buffer);
1057 file_reader = .init(file, io, &file_reader_buffer);
10551058 file_name = undefined; // it aliases file_reader_buffer
10561059
10571060 const header = elf.Header.read(&file_reader.interface) catch |hdr_err| switch (hdr_err) {
......@@ -1101,7 +1104,6 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
11011104 error.SymLinkLoop,
11021105 error.ProcessFdQuotaExceeded,
11031106 error.SystemFdQuotaExceeded,
1104 error.ProcessNotFound,
11051107 error.Canceled,
11061108 => |e| return e,
11071109
lib/std/zig/system/NativePaths.zig+7-6
......@@ -1,11 +1,12 @@
1const std = @import("../../std.zig");
1const NativePaths = @This();
22const builtin = @import("builtin");
3
4const std = @import("../../std.zig");
5const Io = std.Io;
36const Allocator = std.mem.Allocator;
47const process = std.process;
58const mem = std.mem;
69
7const NativePaths = @This();
8
910arena: Allocator,
1011include_dirs: std.ArrayList([]const u8) = .empty,
1112lib_dirs: std.ArrayList([]const u8) = .empty,
......@@ -13,7 +14,7 @@ framework_dirs: std.ArrayList([]const u8) = .empty,
1314rpaths: std.ArrayList([]const u8) = .empty,
1415warnings: std.ArrayList([]const u8) = .empty,
1516
16pub fn detect(arena: Allocator, native_target: *const std.Target) !NativePaths {
17pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !NativePaths {
1718 var self: NativePaths = .{ .arena = arena };
1819 var is_nix = false;
1920 if (process.getEnvVarOwned(arena, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
......@@ -115,8 +116,8 @@ pub fn detect(arena: Allocator, native_target: *const std.Target) !NativePaths {
115116
116117 // TODO: consider also adding macports paths
117118 if (builtin.target.os.tag.isDarwin()) {
118 if (std.zig.system.darwin.isSdkInstalled(arena)) sdk: {
119 const sdk = std.zig.system.darwin.getSdk(arena, native_target) orelse break :sdk;
119 if (std.zig.system.darwin.isSdkInstalled(arena, io)) sdk: {
120 const sdk = std.zig.system.darwin.getSdk(arena, io, native_target) orelse break :sdk;
120121 try self.addLibDir(try std.fs.path.join(arena, &.{ sdk, "usr/lib" }));
121122 try self.addFrameworkDir(try std.fs.path.join(arena, &.{ sdk, "System/Library/Frameworks" }));
122123 try self.addIncludeDir(try std.fs.path.join(arena, &.{ sdk, "usr/include" }));
lib/std/zig/system/darwin.zig+15-14
......@@ -1,28 +1,29 @@
11const std = @import("std");
2const Io = std.Io;
23const mem = std.mem;
3const Allocator = mem.Allocator;
4const Allocator = std.mem.Allocator;
45const Target = std.Target;
56const Version = std.SemanticVersion;
67
78pub const macos = @import("darwin/macos.zig");
89
910/// Check if SDK is installed on Darwin without triggering CLT installation popup window.
10/// Note: simply invoking `xcrun` will inevitably trigger the CLT installation popup.
11///
12/// Simply invoking `xcrun` will inevitably trigger the CLT installation popup.
1113/// Therefore, we resort to invoking `xcode-select --print-path` and checking
1214/// if the status is nonzero.
15///
1316/// stderr from xcode-select is ignored.
17///
1418/// If error.OutOfMemory occurs in Allocator, this function returns null.
15pub fn isSdkInstalled(allocator: Allocator) bool {
16 const result = std.process.Child.run(.{
17 .allocator = allocator,
19pub fn isSdkInstalled(gpa: Allocator, io: Io) bool {
20 const result = std.process.Child.run(gpa, io, .{
1821 .argv = &.{ "xcode-select", "--print-path" },
1922 }) catch return false;
20
2123 defer {
22 allocator.free(result.stderr);
23 allocator.free(result.stdout);
24 gpa.free(result.stderr);
25 gpa.free(result.stdout);
2426 }
25
2627 return switch (result.term) {
2728 .Exited => |code| if (code == 0) result.stdout.len > 0 else false,
2829 else => false,
......@@ -34,7 +35,7 @@ pub fn isSdkInstalled(allocator: Allocator) bool {
3435/// Caller owns the memory.
3536/// stderr from xcrun is ignored.
3637/// If error.OutOfMemory occurs in Allocator, this function returns null.
37pub fn getSdk(allocator: Allocator, target: *const Target) ?[]const u8 {
38pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 {
3839 const is_simulator_abi = target.abi == .simulator;
3940 const sdk = switch (target.os.tag) {
4041 .driverkit => "driverkit",
......@@ -46,16 +47,16 @@ pub fn getSdk(allocator: Allocator, target: *const Target) ?[]const u8 {
4647 else => return null,
4748 };
4849 const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" };
49 const result = std.process.Child.run(.{ .allocator = allocator, .argv = argv }) catch return null;
50 const result = std.process.Child.run(gpa, io, .{ .argv = argv }) catch return null;
5051 defer {
51 allocator.free(result.stderr);
52 allocator.free(result.stdout);
52 gpa.free(result.stderr);
53 gpa.free(result.stdout);
5354 }
5455 switch (result.term) {
5556 .Exited => |code| if (code != 0) return null,
5657 else => return null,
5758 }
58 return allocator.dupe(u8, mem.trimEnd(u8, result.stdout, "\r\n")) catch null;
59 return gpa.dupe(u8, mem.trimEnd(u8, result.stdout, "\r\n")) catch null;
5960}
6061
6162test {
lib/std/zig/system/darwin/macos.zig+5-4
......@@ -1,14 +1,15 @@
1const std = @import("std");
21const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
35const assert = std.debug.assert;
46const mem = std.mem;
57const testing = std.testing;
6
78const Target = std.Target;
89
910/// Detect macOS version.
1011/// `target_os` is not modified in case of error.
11pub fn detect(target_os: *Target.Os) !void {
12pub fn detect(io: Io, target_os: *Target.Os) !void {
1213 // Drop use of osproductversion sysctl because:
1314 // 1. only available 10.13.4 High Sierra and later
1415 // 2. when used from a binary built against < SDK 11.0 it returns 10.16 and masks Big Sur 11.x version
......@@ -54,7 +55,7 @@ pub fn detect(target_os: *Target.Os) !void {
5455 // approx. 4 times historical file size
5556 var buf: [2048]u8 = undefined;
5657
57 if (std.fs.cwd().readFile(path, &buf)) |bytes| {
58 if (Io.Dir.cwd().readFile(io, path, &buf)) |bytes| {
5859 if (parseSystemVersion(bytes)) |ver| {
5960 // never return non-canonical `10.(16+)`
6061 if (!(ver.major == 10 and ver.minor >= 16)) {
lib/std/zig/system/linux.zig+2-2
......@@ -444,10 +444,10 @@ inline fn getAArch64CpuFeature(comptime feat_reg: []const u8) u64 {
444444}
445445
446446pub fn detectNativeCpuAndFeatures(io: Io) ?Target.Cpu {
447 var file = fs.openFileAbsolute("/proc/cpuinfo", .{}) catch |err| switch (err) {
447 var file = Io.Dir.openFileAbsolute(io, "/proc/cpuinfo", .{}) catch |err| switch (err) {
448448 else => return null,
449449 };
450 defer file.close();
450 defer file.close(io);
451451
452452 var buffer: [4096]u8 = undefined; // "flags" lines can get pretty long.
453453 var file_reader = file.reader(io, &buffer);
lib/std/zip.zig+16-12
......@@ -4,9 +4,11 @@
44//! Note that this file uses the abbreviation "cd" for "central directory"
55
66const builtin = @import("builtin");
7const std = @import("std");
8const File = std.fs.File;
97const is_le = builtin.target.cpu.arch.endian() == .little;
8
9const std = @import("std");
10const Io = std.Io;
11const File = std.Io.File;
1012const Writer = std.Io.Writer;
1113const Reader = std.Io.Reader;
1214const flate = std.compress.flate;
......@@ -115,7 +117,7 @@ pub const EndRecord = extern struct {
115117 return record;
116118 }
117119
118 pub const FindFileError = File.Reader.SizeError || File.SeekError || File.ReadError || error{
120 pub const FindFileError = File.Reader.SizeError || File.SeekError || File.Reader.Error || error{
119121 ZipNoEndRecord,
120122 EndOfStream,
121123 ReadFailed,
......@@ -460,8 +462,10 @@ pub const Iterator = struct {
460462 stream: *File.Reader,
461463 options: ExtractOptions,
462464 filename_buf: []u8,
463 dest: std.fs.Dir,
465 dest: Io.Dir,
464466 ) !void {
467 const io = stream.io;
468
465469 if (filename_buf.len < self.filename_len)
466470 return error.ZipInsufficientBuffer;
467471 switch (self.compression_method) {
......@@ -550,23 +554,23 @@ pub const Iterator = struct {
550554 if (filename[filename.len - 1] == '/') {
551555 if (self.uncompressed_size != 0)
552556 return error.ZipBadDirectorySize;
553 try dest.makePath(filename[0 .. filename.len - 1]);
557 try dest.createDirPath(io, filename[0 .. filename.len - 1]);
554558 return;
555559 }
556560
557561 const out_file = blk: {
558562 if (std.fs.path.dirname(filename)) |dirname| {
559 var parent_dir = try dest.makeOpenPath(dirname, .{});
560 defer parent_dir.close();
563 var parent_dir = try dest.createDirPathOpen(io, dirname, .{});
564 defer parent_dir.close(io);
561565
562566 const basename = std.fs.path.basename(filename);
563 break :blk try parent_dir.createFile(basename, .{ .exclusive = true });
567 break :blk try parent_dir.createFile(io, basename, .{ .exclusive = true });
564568 }
565 break :blk try dest.createFile(filename, .{ .exclusive = true });
569 break :blk try dest.createFile(io, filename, .{ .exclusive = true });
566570 };
567 defer out_file.close();
571 defer out_file.close(io);
568572 var out_file_buffer: [1024]u8 = undefined;
569 var file_writer = out_file.writer(&out_file_buffer);
573 var file_writer = out_file.writer(io, &out_file_buffer);
570574 const local_data_file_offset: u64 =
571575 @as(u64, self.file_offset) +
572576 @as(u64, @sizeOf(LocalFileHeader)) +
......@@ -647,7 +651,7 @@ pub const ExtractOptions = struct {
647651};
648652
649653/// Extract the zipped files to the given `dest` directory.
650pub fn extract(dest: std.fs.Dir, fr: *File.Reader, options: ExtractOptions) !void {
654pub fn extract(dest: Io.Dir, fr: *File.Reader, options: ExtractOptions) !void {
651655 if (options.verify_checksums) @panic("TODO unimplemented");
652656
653657 var iter = try Iterator.init(fr);
src/Air/print.zig+18-10
......@@ -9,7 +9,7 @@ const Type = @import("../Type.zig");
99const Air = @import("../Air.zig");
1010const InternPool = @import("../InternPool.zig");
1111
12pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
12pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air.Liveness) !void {
1313 comptime assert(build_options.enable_debug_extensions);
1414 const instruction_bytes = air.instructions.len *
1515 // Here we don't use @sizeOf(Air.Inst.Data) because it would include
......@@ -24,7 +24,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air
2424 liveness_special_bytes + tomb_bytes;
2525
2626 // zig fmt: off
27 stream.print(
27 try stream.print(
2828 \\# Total AIR+Liveness bytes: {Bi}
2929 \\# AIR Instructions: {d} ({Bi})
3030 \\# AIR Extra Data: {d} ({Bi})
......@@ -39,7 +39,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air
3939 tomb_bytes,
4040 if (liveness) |l| l.extra.len else 0, liveness_extra_bytes,
4141 if (liveness) |l| l.special.count() else 0, liveness_special_bytes,
42 }) catch return;
42 });
4343 // zig fmt: on
4444
4545 var writer: Writer = .{
......@@ -50,7 +50,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air
5050 .indent = 2,
5151 .skip_body = false,
5252 };
53 writer.writeBody(stream, air.getMainBody()) catch return;
53 try writer.writeBody(stream, air.getMainBody());
5454}
5555
5656pub fn writeInst(
......@@ -73,15 +73,23 @@ pub fn writeInst(
7373}
7474
7575pub fn dump(air: Air, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
76 const stderr_bw, _ = std.debug.lockStderrWriter(&.{});
77 defer std.debug.unlockStderrWriter();
78 air.write(stderr_bw, pt, liveness);
76 const comp = pt.zcu.comp;
77 const io = comp.io;
78 var buffer: [512]u8 = undefined;
79 const stderr = try io.lockStderr(&buffer, null);
80 defer io.unlockStderr();
81 const w = &stderr.file_writer.interface;
82 air.write(w, pt, liveness);
7983}
8084
8185pub fn dumpInst(air: Air, inst: Air.Inst.Index, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
82 const stderr_bw, _ = std.debug.lockStderrWriter(&.{});
83 defer std.debug.unlockStderrWriter();
84 air.writeInst(stderr_bw, inst, pt, liveness);
86 const comp = pt.zcu.comp;
87 const io = comp.io;
88 var buffer: [512]u8 = undefined;
89 const stderr = try io.lockStderr(&buffer, null);
90 defer io.unlockStderr();
91 const w = &stderr.file_writer.interface;
92 air.writeInst(w, inst, pt, liveness);
8593}
8694
8795const Writer = struct {
src/Builtin.zig+3-2
......@@ -313,8 +313,9 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {
313313 assert(file.source != null);
314314
315315 const root_dir, const sub_path = file.path.openInfo(comp.dirs);
316 const io = comp.io;
316317
317 if (root_dir.statFile(sub_path)) |stat| {
318 if (root_dir.statFile(io, sub_path, .{})) |stat| {
318319 if (stat.size != file.source.?.len) {
319320 std.log.warn(
320321 "the cached file '{f}' had the wrong size. Expected {d}, found {d}. " ++
......@@ -342,7 +343,7 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {
342343 }
343344
344345 // `make_path` matters because the dir hasn't actually been created yet.
345 var af = try root_dir.atomicFile(sub_path, .{ .make_path = true, .write_buffer = &.{} });
346 var af = try root_dir.atomicFile(io, sub_path, .{ .make_path = true, .write_buffer = &.{} });
346347 defer af.deinit();
347348 try af.file_writer.interface.writeAll(file.source.?);
348349 af.finish() catch |err| switch (err) {
src/Compilation.zig+230-206
......@@ -446,11 +446,11 @@ pub const Path = struct {
446446 }
447447
448448 /// Given a `Path`, returns the directory handle and sub path to be used to open the path.
449 pub fn openInfo(p: Path, dirs: Directories) struct { fs.Dir, []const u8 } {
449 pub fn openInfo(p: Path, dirs: Directories) struct { Io.Dir, []const u8 } {
450450 const dir = switch (p.root) {
451451 .none => {
452452 const cwd_sub_path = absToCwdRelative(p.sub_path, dirs.cwd);
453 return .{ fs.cwd(), cwd_sub_path };
453 return .{ Io.Dir.cwd(), cwd_sub_path };
454454 },
455455 .zig_lib => dirs.zig_lib.handle,
456456 .global_cache => dirs.global_cache.handle,
......@@ -721,13 +721,13 @@ pub const Directories = struct {
721721 /// This may be the same as `global_cache`.
722722 local_cache: Cache.Directory,
723723
724 pub fn deinit(dirs: *Directories) void {
724 pub fn deinit(dirs: *Directories, io: Io) void {
725725 // The local and global caches could be the same.
726 const close_local = dirs.local_cache.handle.fd != dirs.global_cache.handle.fd;
726 const close_local = dirs.local_cache.handle.handle != dirs.global_cache.handle.handle;
727727
728 dirs.global_cache.handle.close();
729 if (close_local) dirs.local_cache.handle.close();
730 dirs.zig_lib.handle.close();
728 dirs.global_cache.handle.close(io);
729 if (close_local) dirs.local_cache.handle.close(io);
730 dirs.zig_lib.handle.close(io);
731731 }
732732
733733 /// Returns a `Directories` where `local_cache` is replaced with `global_cache`, intended for
......@@ -745,6 +745,7 @@ pub const Directories = struct {
745745 /// Uses `std.process.fatal` on error conditions.
746746 pub fn init(
747747 arena: Allocator,
748 io: Io,
748749 override_zig_lib: ?[]const u8,
749750 override_global_cache: ?[]const u8,
750751 local_cache_strat: union(enum) {
......@@ -768,30 +769,30 @@ pub const Directories = struct {
768769 };
769770
770771 const zig_lib: Cache.Directory = d: {
771 if (override_zig_lib) |path| break :d openUnresolved(arena, cwd, path, .@"zig lib");
772 if (override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib");
772773 if (wasi) break :d openWasiPreopen(wasi_preopens, "/lib");
773 break :d introspect.findZigLibDirFromSelfExe(arena, cwd, self_exe_path) catch |err| {
774 fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) });
774 break :d introspect.findZigLibDirFromSelfExe(arena, io, cwd, self_exe_path) catch |err| {
775 fatal("unable to find zig installation directory '{s}': {t}", .{ self_exe_path, err });
775776 };
776777 };
777778
778779 const global_cache: Cache.Directory = d: {
779 if (override_global_cache) |path| break :d openUnresolved(arena, cwd, path, .@"global cache");
780 if (override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache");
780781 if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache");
781782 const path = introspect.resolveGlobalCacheDir(arena) catch |err| {
782 fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)});
783 fatal("unable to resolve zig cache directory: {t}", .{err});
783784 };
784 break :d openUnresolved(arena, cwd, path, .@"global cache");
785 break :d openUnresolved(arena, io, cwd, path, .@"global cache");
785786 };
786787
787788 const local_cache: Cache.Directory = switch (local_cache_strat) {
788 .override => |path| openUnresolved(arena, cwd, path, .@"local cache"),
789 .override => |path| openUnresolved(arena, io, cwd, path, .@"local cache"),
789790 .search => d: {
790 const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, cwd) catch |err| {
791 fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)});
791 const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, io, cwd) catch |err| {
792 fatal("unable to resolve zig cache directory: {t}", .{err});
792793 };
793794 const path = maybe_path orelse break :d global_cache;
794 break :d openUnresolved(arena, cwd, path, .@"local cache");
795 break :d openUnresolved(arena, io, cwd, path, .@"local cache");
795796 },
796797 .global => global_cache,
797798 };
......@@ -814,18 +815,24 @@ pub const Directories = struct {
814815 return .{
815816 .path = if (std.mem.eql(u8, name, ".")) null else name,
816817 .handle = .{
817 .fd = preopens.find(name) orelse fatal("WASI preopen not found: '{s}'", .{name}),
818 .handle = preopens.find(name) orelse fatal("WASI preopen not found: '{s}'", .{name}),
818819 },
819820 };
820821 }
821 fn openUnresolved(arena: Allocator, cwd: []const u8, unresolved_path: []const u8, thing: enum { @"zig lib", @"global cache", @"local cache" }) Cache.Directory {
822 fn openUnresolved(
823 arena: Allocator,
824 io: Io,
825 cwd: []const u8,
826 unresolved_path: []const u8,
827 thing: enum { @"zig lib", @"global cache", @"local cache" },
828 ) Cache.Directory {
822829 const path = introspect.resolvePath(arena, cwd, &.{unresolved_path}) catch |err| {
823830 fatal("unable to resolve {s} directory: {s}", .{ @tagName(thing), @errorName(err) });
824831 };
825832 const nonempty_path = if (path.len == 0) "." else path;
826833 const handle_or_err = switch (thing) {
827 .@"zig lib" => fs.cwd().openDir(nonempty_path, .{}),
828 .@"global cache", .@"local cache" => fs.cwd().makeOpenPath(nonempty_path, .{}),
834 .@"zig lib" => Io.Dir.cwd().openDir(io, nonempty_path, .{}),
835 .@"global cache", .@"local cache" => Io.Dir.cwd().createDirPathOpen(io, nonempty_path, .{}),
829836 };
830837 return .{
831838 .path = if (path.len == 0) null else path,
......@@ -912,8 +919,8 @@ pub const CrtFile = struct {
912919 lock: Cache.Lock,
913920 full_object_path: Cache.Path,
914921
915 pub fn deinit(self: *CrtFile, gpa: Allocator) void {
916 self.lock.release();
922 pub fn deinit(self: *CrtFile, gpa: Allocator, io: Io) void {
923 self.lock.release(io);
917924 gpa.free(self.full_object_path.sub_path);
918925 self.* = undefined;
919926 }
......@@ -1104,8 +1111,8 @@ pub const CObject = struct {
11041111 const source_line = source_line: {
11051112 if (diag.src_loc.offset == 0 or diag.src_loc.column == 0) break :source_line 0;
11061113
1107 const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0;
1108 defer file.close();
1114 const file = Io.Dir.cwd().openFile(io, file_name, .{}) catch break :source_line 0;
1115 defer file.close(io);
11091116 var buffer: [1024]u8 = undefined;
11101117 var file_reader = file.reader(io, &buffer);
11111118 file_reader.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0;
......@@ -1179,8 +1186,8 @@ pub const CObject = struct {
11791186 };
11801187
11811188 var buffer: [1024]u8 = undefined;
1182 const file = try fs.cwd().openFile(path, .{});
1183 defer file.close();
1189 const file = try Io.Dir.cwd().openFile(io, path, .{});
1190 defer file.close(io);
11841191 var file_reader = file.reader(io, &buffer);
11851192 var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface });
11861193 defer bc.deinit();
......@@ -1310,7 +1317,7 @@ pub const CObject = struct {
13101317 };
13111318
13121319 /// Returns if there was failure.
1313 pub fn clearStatus(self: *CObject, gpa: Allocator) bool {
1320 pub fn clearStatus(self: *CObject, gpa: Allocator, io: Io) bool {
13141321 switch (self.status) {
13151322 .new => return false,
13161323 .failure, .failure_retryable => {
......@@ -1319,15 +1326,15 @@ pub const CObject = struct {
13191326 },
13201327 .success => |*success| {
13211328 gpa.free(success.object_path.sub_path);
1322 success.lock.release();
1329 success.lock.release(io);
13231330 self.status = .new;
13241331 return false;
13251332 },
13261333 }
13271334 }
13281335
1329 pub fn destroy(self: *CObject, gpa: Allocator) void {
1330 _ = self.clearStatus(gpa);
1336 pub fn destroy(self: *CObject, gpa: Allocator, io: Io) void {
1337 _ = self.clearStatus(gpa, io);
13311338 gpa.destroy(self);
13321339 }
13331340};
......@@ -1357,7 +1364,7 @@ pub const Win32Resource = struct {
13571364 },
13581365
13591366 /// Returns true if there was failure.
1360 pub fn clearStatus(self: *Win32Resource, gpa: Allocator) bool {
1367 pub fn clearStatus(self: *Win32Resource, gpa: Allocator, io: Io) bool {
13611368 switch (self.status) {
13621369 .new => return false,
13631370 .failure, .failure_retryable => {
......@@ -1366,15 +1373,15 @@ pub const Win32Resource = struct {
13661373 },
13671374 .success => |*success| {
13681375 gpa.free(success.res_path);
1369 success.lock.release();
1376 success.lock.release(io);
13701377 self.status = .new;
13711378 return false;
13721379 },
13731380 }
13741381 }
13751382
1376 pub fn destroy(self: *Win32Resource, gpa: Allocator) void {
1377 _ = self.clearStatus(gpa);
1383 pub fn destroy(self: *Win32Resource, gpa: Allocator, io: Io) void {
1384 _ = self.clearStatus(gpa, io);
13781385 gpa.destroy(self);
13791386 }
13801387};
......@@ -1603,9 +1610,9 @@ const CacheUse = union(CacheMode) {
16031610 /// Prevents other processes from clobbering files in the output directory.
16041611 lock: ?Cache.Lock,
16051612
1606 fn releaseLock(whole: *Whole) void {
1613 fn releaseLock(whole: *Whole, io: Io) void {
16071614 if (whole.lock) |*lock| {
1608 lock.release();
1615 lock.release(io);
16091616 whole.lock = null;
16101617 }
16111618 }
......@@ -1617,17 +1624,17 @@ const CacheUse = union(CacheMode) {
16171624 }
16181625 };
16191626
1620 fn deinit(cu: CacheUse) void {
1627 fn deinit(cu: CacheUse, io: Io) void {
16211628 switch (cu) {
16221629 .none => |none| {
16231630 assert(none.tmp_artifact_directory == null);
16241631 },
16251632 .incremental => |incremental| {
1626 incremental.artifact_directory.handle.close();
1633 incremental.artifact_directory.handle.close(io);
16271634 },
16281635 .whole => |whole| {
16291636 assert(whole.tmp_artifact_directory == null);
1630 whole.releaseLock();
1637 whole.releaseLock(io);
16311638 },
16321639 }
16331640 }
......@@ -1872,7 +1879,7 @@ pub const CreateDiagnostic = union(enum) {
18721879 pub const CreateCachePath = struct {
18731880 which: enum { local, global },
18741881 sub: []const u8,
1875 err: (fs.Dir.MakeError || fs.Dir.OpenError || fs.Dir.StatFileError),
1882 err: (Io.Dir.CreateDirError || Io.Dir.OpenError || Io.Dir.StatFileError),
18761883 };
18771884 pub fn format(diag: CreateDiagnostic, w: *Writer) Writer.Error!void {
18781885 switch (diag) {
......@@ -1896,13 +1903,17 @@ pub const CreateDiagnostic = union(enum) {
18961903 return error.CreateFail;
18971904 }
18981905};
1899pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, options: CreateOptions) error{
1906
1907pub const CreateError = error{
19001908 OutOfMemory,
1909 Canceled,
19011910 Unexpected,
19021911 CurrentWorkingDirectoryUnlinked,
19031912 /// An error has been stored to `diag`.
19041913 CreateFail,
1905}!*Compilation {
1914};
1915
1916pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, options: CreateOptions) CreateError!*Compilation {
19061917 const output_mode = options.config.output_mode;
19071918 const is_dyn_lib = switch (output_mode) {
19081919 .Obj, .Exe => false,
......@@ -1950,6 +1961,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
19501961
19511962 const libc_dirs = std.zig.LibCDirs.detect(
19521963 arena,
1964 io,
19531965 options.dirs.zig_lib.path.?,
19541966 target,
19551967 options.root_mod.resolved_target.is_native_abi,
......@@ -2080,13 +2092,17 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
20802092 }
20812093
20822094 if (options.verbose_llvm_cpu_features) {
2083 if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: {
2084 const stderr_w, _ = std.debug.lockStderrWriter(&.{});
2085 defer std.debug.unlockStderrWriter();
2086 stderr_w.print("compilation: {s}\n", .{options.root_name}) catch break :print;
2087 stderr_w.print(" target: {s}\n", .{try target.zigTriple(arena)}) catch break :print;
2088 stderr_w.print(" cpu: {s}\n", .{target.cpu.model.name}) catch break :print;
2089 stderr_w.print(" features: {s}\n", .{cf}) catch {};
2095 if (options.root_mod.resolved_target.llvm_cpu_features) |cf| {
2096 const stderr = try io.lockStderr(&.{}, null);
2097 defer io.unlockStderr();
2098 const w = &stderr.file_writer.interface;
2099 printVerboseLlvmCpuFeatures(w, arena, options.root_name, target, cf) catch |err| switch (err) {
2100 error.WriteFailed => switch (stderr.file_writer.err.?) {
2101 error.Canceled => |e| return e,
2102 else => {},
2103 },
2104 error.OutOfMemory => |e| return e,
2105 };
20902106 }
20912107 }
20922108
......@@ -2104,16 +2120,16 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
21042120 cache.* = .{
21052121 .gpa = gpa,
21062122 .io = io,
2107 .manifest_dir = options.dirs.local_cache.handle.makeOpenPath("h", .{}) catch |err| {
2123 .manifest_dir = options.dirs.local_cache.handle.createDirPathOpen(io, "h", .{}) catch |err| {
21082124 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } });
21092125 },
21102126 };
21112127 // These correspond to std.zig.Server.Message.PathPrefix.
2112 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
2128 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
21132129 cache.addPrefix(options.dirs.zig_lib);
21142130 cache.addPrefix(options.dirs.local_cache);
21152131 cache.addPrefix(options.dirs.global_cache);
2116 errdefer cache.manifest_dir.close();
2132 errdefer cache.manifest_dir.close(io);
21172133
21182134 // This is shared hasher state common to zig source and all C source files.
21192135 cache.hash.addBytes(build_options.version);
......@@ -2154,18 +2170,18 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
21542170 // to redundantly happen for each AstGen operation.
21552171 const zir_sub_dir = "z";
21562172
2157 var local_zir_dir = options.dirs.local_cache.handle.makeOpenPath(zir_sub_dir, .{}) catch |err| {
2173 var local_zir_dir = options.dirs.local_cache.handle.createDirPathOpen(io, zir_sub_dir, .{}) catch |err| {
21582174 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = zir_sub_dir, .err = err } });
21592175 };
2160 errdefer local_zir_dir.close();
2176 errdefer local_zir_dir.close(io);
21612177 const local_zir_cache: Cache.Directory = .{
21622178 .handle = local_zir_dir,
21632179 .path = try options.dirs.local_cache.join(arena, &.{zir_sub_dir}),
21642180 };
2165 var global_zir_dir = options.dirs.global_cache.handle.makeOpenPath(zir_sub_dir, .{}) catch |err| {
2181 var global_zir_dir = options.dirs.global_cache.handle.createDirPathOpen(io, zir_sub_dir, .{}) catch |err| {
21662182 return diag.fail(.{ .create_cache_path = .{ .which = .global, .sub = zir_sub_dir, .err = err } });
21672183 };
2168 errdefer global_zir_dir.close();
2184 errdefer global_zir_dir.close(io);
21692185 const global_zir_cache: Cache.Directory = .{
21702186 .handle = global_zir_dir,
21712187 .path = try options.dirs.global_cache.join(arena, &.{zir_sub_dir}),
......@@ -2433,10 +2449,10 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
24332449 const digest = hash.final();
24342450
24352451 const artifact_sub_dir = "o" ++ fs.path.sep_str ++ digest;
2436 var artifact_dir = options.dirs.local_cache.handle.makeOpenPath(artifact_sub_dir, .{}) catch |err| {
2452 var artifact_dir = options.dirs.local_cache.handle.createDirPathOpen(io, artifact_sub_dir, .{}) catch |err| {
24372453 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = artifact_sub_dir, .err = err } });
24382454 };
2439 errdefer artifact_dir.close();
2455 errdefer artifact_dir.close(io);
24402456 const artifact_directory: Cache.Directory = .{
24412457 .handle = artifact_dir,
24422458 .path = try options.dirs.local_cache.join(arena, &.{artifact_sub_dir}),
......@@ -2687,12 +2703,26 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
26872703 return comp;
26882704}
26892705
2706fn printVerboseLlvmCpuFeatures(
2707 w: *Writer,
2708 arena: Allocator,
2709 root_name: []const u8,
2710 target: *const std.Target,
2711 cf: [*:0]const u8,
2712) (Writer.Error || Allocator.Error)!void {
2713 try w.print("compilation: {s}\n", .{root_name});
2714 try w.print(" target: {s}\n", .{try target.zigTriple(arena)});
2715 try w.print(" cpu: {s}\n", .{target.cpu.model.name});
2716 try w.print(" features: {s}\n", .{cf});
2717}
2718
26902719pub fn destroy(comp: *Compilation) void {
26912720 const gpa = comp.gpa;
2721 const io = comp.io;
26922722
26932723 if (comp.bin_file) |lf| lf.destroy();
26942724 if (comp.zcu) |zcu| zcu.deinit();
2695 comp.cache_use.deinit();
2725 comp.cache_use.deinit(io);
26962726
26972727 for (&comp.work_queues) |*work_queue| work_queue.deinit(gpa);
26982728 comp.c_object_work_queue.deinit(gpa);
......@@ -2705,36 +2735,36 @@ pub fn destroy(comp: *Compilation) void {
27052735 var it = comp.crt_files.iterator();
27062736 while (it.next()) |entry| {
27072737 gpa.free(entry.key_ptr.*);
2708 entry.value_ptr.deinit(gpa);
2738 entry.value_ptr.deinit(gpa, io);
27092739 }
27102740 comp.crt_files.deinit(gpa);
27112741 }
2712 if (comp.libcxx_static_lib) |*crt_file| crt_file.deinit(gpa);
2713 if (comp.libcxxabi_static_lib) |*crt_file| crt_file.deinit(gpa);
2714 if (comp.libunwind_static_lib) |*crt_file| crt_file.deinit(gpa);
2715 if (comp.tsan_lib) |*crt_file| crt_file.deinit(gpa);
2716 if (comp.ubsan_rt_lib) |*crt_file| crt_file.deinit(gpa);
2717 if (comp.ubsan_rt_obj) |*crt_file| crt_file.deinit(gpa);
2718 if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa);
2719 if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa);
2720 if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa);
2721 if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa);
2722 if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa);
2742 if (comp.libcxx_static_lib) |*crt_file| crt_file.deinit(gpa, io);
2743 if (comp.libcxxabi_static_lib) |*crt_file| crt_file.deinit(gpa, io);
2744 if (comp.libunwind_static_lib) |*crt_file| crt_file.deinit(gpa, io);
2745 if (comp.tsan_lib) |*crt_file| crt_file.deinit(gpa, io);
2746 if (comp.ubsan_rt_lib) |*crt_file| crt_file.deinit(gpa, io);
2747 if (comp.ubsan_rt_obj) |*crt_file| crt_file.deinit(gpa, io);
2748 if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa, io);
2749 if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa, io);
2750 if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa, io);
2751 if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa, io);
2752 if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa, io);
27232753
27242754 if (comp.glibc_so_files) |*glibc_file| {
2725 glibc_file.deinit(gpa);
2755 glibc_file.deinit(gpa, io);
27262756 }
27272757
27282758 if (comp.freebsd_so_files) |*freebsd_file| {
2729 freebsd_file.deinit(gpa);
2759 freebsd_file.deinit(gpa, io);
27302760 }
27312761
27322762 if (comp.netbsd_so_files) |*netbsd_file| {
2733 netbsd_file.deinit(gpa);
2763 netbsd_file.deinit(gpa, io);
27342764 }
27352765
27362766 for (comp.c_object_table.keys()) |key| {
2737 key.destroy(gpa);
2767 key.destroy(gpa, io);
27382768 }
27392769 comp.c_object_table.deinit(gpa);
27402770
......@@ -2744,7 +2774,7 @@ pub fn destroy(comp: *Compilation) void {
27442774 comp.failed_c_objects.deinit(gpa);
27452775
27462776 for (comp.win32_resource_table.keys()) |key| {
2747 key.destroy(gpa);
2777 key.destroy(gpa, io);
27482778 }
27492779 comp.win32_resource_table.deinit(gpa);
27502780
......@@ -2760,7 +2790,7 @@ pub fn destroy(comp: *Compilation) void {
27602790
27612791 comp.clearMiscFailures();
27622792
2763 comp.cache_parent.manifest_dir.close();
2793 comp.cache_parent.manifest_dir.close(io);
27642794}
27652795
27662796pub fn clearMiscFailures(comp: *Compilation) void {
......@@ -2791,10 +2821,12 @@ pub fn hotCodeSwap(
27912821}
27922822
27932823fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void {
2824 const io = comp.io;
2825
27942826 switch (comp.cache_use) {
27952827 .none => |none| {
27962828 if (none.tmp_artifact_directory) |*tmp_dir| {
2797 tmp_dir.handle.close();
2829 tmp_dir.handle.close(io);
27982830 none.tmp_artifact_directory = null;
27992831 if (dev.env == .bootstrap) {
28002832 // zig1 uses `CacheMode.none`, but it doesn't need to know how to delete
......@@ -2813,12 +2845,9 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void {
28132845 return;
28142846 }
28152847 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
2816 comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| {
2817 log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{
2818 comp.dirs.local_cache.path orelse ".",
2819 fs.path.sep,
2820 tmp_dir_sub_path,
2821 @errorName(err),
2848 comp.dirs.local_cache.handle.deleteTree(io, tmp_dir_sub_path) catch |err| {
2849 log.warn("failed to delete temporary directory '{s}{c}{s}': {t}", .{
2850 comp.dirs.local_cache.path orelse ".", fs.path.sep, tmp_dir_sub_path, err,
28222851 });
28232852 };
28242853 }
......@@ -2834,15 +2863,12 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void {
28342863 comp.bin_file = null;
28352864 }
28362865 if (whole.tmp_artifact_directory) |*tmp_dir| {
2837 tmp_dir.handle.close();
2866 tmp_dir.handle.close(io);
28382867 whole.tmp_artifact_directory = null;
28392868 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
2840 comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| {
2841 log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{
2842 comp.dirs.local_cache.path orelse ".",
2843 fs.path.sep,
2844 tmp_dir_sub_path,
2845 @errorName(err),
2869 comp.dirs.local_cache.handle.deleteTree(io, tmp_dir_sub_path) catch |err| {
2870 log.warn("failed to delete temporary directory '{s}{c}{s}': {t}", .{
2871 comp.dirs.local_cache.path orelse ".", fs.path.sep, tmp_dir_sub_path, err,
28462872 });
28472873 };
28482874 }
......@@ -2891,7 +2917,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
28912917 tmp_dir_rand_int = std.crypto.random.int(u64);
28922918 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
28932919 const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});
2894 const handle = comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}) catch |err| {
2920 const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| {
28952921 return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err });
28962922 };
28972923 break :d .{ .path = path, .handle = handle };
......@@ -2901,7 +2927,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
29012927 .whole => |whole| {
29022928 assert(comp.bin_file == null);
29032929 // We are about to obtain this lock, so here we give other processes a chance first.
2904 whole.releaseLock();
2930 whole.releaseLock(io);
29052931
29062932 man = comp.cache_parent.obtain();
29072933 whole.cache_manifest = &man;
......@@ -2972,7 +2998,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
29722998 tmp_dir_rand_int = std.crypto.random.int(u64);
29732999 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
29743000 const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});
2975 const handle = comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}) catch |err| {
3001 const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| {
29763002 return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err });
29773003 };
29783004 break :d .{ .path = path, .handle = handle };
......@@ -3087,17 +3113,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
30873113 }
30883114
30893115 if (build_options.enable_debug_extensions and comp.verbose_intern_pool) {
3090 std.debug.print("intern pool stats for '{s}':\n", .{
3091 comp.root_name,
3092 });
3116 std.debug.print("intern pool stats for '{s}':\n", .{comp.root_name});
30933117 zcu.intern_pool.dump();
30943118 }
30953119
30963120 if (build_options.enable_debug_extensions and comp.verbose_generic_instances) {
3097 std.debug.print("generic instances for '{s}:0x{x}':\n", .{
3098 comp.root_name,
3099 @intFromPtr(zcu),
3100 });
3121 std.debug.print("generic instances for '{s}:0x{x}':\n", .{ comp.root_name, @intFromPtr(zcu) });
31013122 zcu.intern_pool.dumpGenericInstances(gpa);
31023123 }
31033124 }
......@@ -3152,7 +3173,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
31523173 // the file handle and re-open it in the follow up call to
31533174 // `makeWritable`.
31543175 if (lf.file) |f| {
3155 f.close();
3176 f.close(io);
31563177 lf.file = null;
31573178
31583179 if (lf.closeDebugInfo()) break :w .lf_and_debug;
......@@ -3165,12 +3186,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
31653186
31663187 // Rename the temporary directory into place.
31673188 // Close tmp dir and link.File to avoid open handle during rename.
3168 whole.tmp_artifact_directory.?.handle.close();
3189 whole.tmp_artifact_directory.?.handle.close(io);
31693190 whole.tmp_artifact_directory = null;
31703191 const s = fs.path.sep_str;
31713192 const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int);
31723193 const o_sub_path = "o" ++ s ++ hex_digest;
3173 renameTmpIntoCache(comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| {
3194 renameTmpIntoCache(io, comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| {
31743195 return comp.setMiscFailure(
31753196 .rename_results,
31763197 "failed to rename compilation results ('{f}{s}') into local cache ('{f}{s}'): {t}",
......@@ -3300,11 +3321,8 @@ pub fn resolveEmitPathFlush(
33003321 },
33013322 }
33023323}
3303fn flush(
3304 comp: *Compilation,
3305 arena: Allocator,
3306 tid: Zcu.PerThread.Id,
3307) Allocator.Error!void {
3324
3325fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancelable || Allocator.Error)!void {
33083326 const io = comp.io;
33093327 if (comp.zcu) |zcu| {
33103328 if (zcu.llvm_object) |llvm_object| {
......@@ -3370,7 +3388,7 @@ fn flush(
33703388 // This is needed before reading the error flags.
33713389 lf.flush(arena, tid, comp.link_prog_node) catch |err| switch (err) {
33723390 error.LinkFailure => {}, // Already reported.
3373 error.OutOfMemory => return error.OutOfMemory,
3391 error.OutOfMemory, error.Canceled => |e| return e,
33743392 };
33753393 }
33763394 if (comp.zcu) |zcu| {
......@@ -3389,17 +3407,19 @@ fn flush(
33893407/// implementation at the bottom of this function.
33903408/// This function is only called when CacheMode is `whole`.
33913409fn renameTmpIntoCache(
3410 io: Io,
33923411 cache_directory: Cache.Directory,
33933412 tmp_dir_sub_path: []const u8,
33943413 o_sub_path: []const u8,
33953414) !void {
33963415 var seen_eaccess = false;
33973416 while (true) {
3398 fs.rename(
3417 Io.Dir.rename(
33993418 cache_directory.handle,
34003419 tmp_dir_sub_path,
34013420 cache_directory.handle,
34023421 o_sub_path,
3422 io,
34033423 ) catch |err| switch (err) {
34043424 // On Windows, rename fails with `AccessDenied` rather than `PathAlreadyExists`.
34053425 // See https://github.com/ziglang/zig/issues/8362
......@@ -3407,17 +3427,17 @@ fn renameTmpIntoCache(
34073427 .windows => {
34083428 if (seen_eaccess) return error.AccessDenied;
34093429 seen_eaccess = true;
3410 try cache_directory.handle.deleteTree(o_sub_path);
3430 try cache_directory.handle.deleteTree(io, o_sub_path);
34113431 continue;
34123432 },
34133433 else => return error.AccessDenied,
34143434 },
34153435 error.PathAlreadyExists => {
3416 try cache_directory.handle.deleteTree(o_sub_path);
3436 try cache_directory.handle.deleteTree(io, o_sub_path);
34173437 continue;
34183438 },
34193439 error.FileNotFound => {
3420 try cache_directory.handle.makePath("o");
3440 try cache_directory.handle.createDirPath(io, "o");
34213441 continue;
34223442 },
34233443 else => |e| return e,
......@@ -3592,6 +3612,7 @@ fn emitFromCObject(
35923612 new_ext: []const u8,
35933613 unresolved_emit_path: []const u8,
35943614) Allocator.Error!void {
3615 const io = comp.io;
35953616 // The dirname and stem (i.e. everything but the extension), of the sub path of the C object.
35963617 // We'll append `new_ext` to it to get the path to the right thing (asm, LLVM IR, etc).
35973618 const c_obj_dir_and_stem: []const u8 = p: {
......@@ -3601,23 +3622,18 @@ fn emitFromCObject(
36013622 };
36023623 const src_path: Cache.Path = .{
36033624 .root_dir = c_obj_path.root_dir,
3604 .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{
3605 c_obj_dir_and_stem,
3606 new_ext,
3607 }),
3625 .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ c_obj_dir_and_stem, new_ext }),
36083626 };
36093627 const emit_path = comp.resolveEmitPath(unresolved_emit_path);
36103628
3611 src_path.root_dir.handle.copyFile(
3629 Io.Dir.copyFile(
3630 src_path.root_dir.handle,
36123631 src_path.sub_path,
36133632 emit_path.root_dir.handle,
36143633 emit_path.sub_path,
3634 io,
36153635 .{},
3616 ) catch |err| log.err("unable to copy '{f}' to '{f}': {s}", .{
3617 src_path,
3618 emit_path,
3619 @errorName(err),
3620 });
3636 ) catch |err| log.err("unable to copy '{f}' to '{f}': {t}", .{ src_path, emit_path, err });
36213637}
36223638
36233639/// Having the file open for writing is problematic as far as executing the
......@@ -3673,6 +3689,7 @@ pub fn saveState(comp: *Compilation) !void {
36733689 const lf = comp.bin_file orelse return;
36743690
36753691 const gpa = comp.gpa;
3692 const io = comp.io;
36763693
36773694 var bufs = std.array_list.Managed([]const u8).init(gpa);
36783695 defer bufs.deinit();
......@@ -3893,7 +3910,7 @@ pub fn saveState(comp: *Compilation) !void {
38933910 // Using an atomic file prevents a crash or power failure from corrupting
38943911 // the previous incremental compilation state.
38953912 var write_buffer: [1024]u8 = undefined;
3896 var af = try lf.emit.root_dir.handle.atomicFile(basename, .{ .write_buffer = &write_buffer });
3913 var af = try lf.emit.root_dir.handle.atomicFile(io, basename, .{ .write_buffer = &write_buffer });
38973914 defer af.deinit();
38983915 try af.file_writer.interface.writeVecAll(bufs.items);
38993916 try af.finish();
......@@ -4251,12 +4268,13 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle {
42514268 // However, we haven't reported any such error.
42524269 // This is a compiler bug.
42534270 print_ctx: {
4254 var stderr_w, _ = std.debug.lockStderrWriter(&.{});
4255 defer std.debug.unlockStderrWriter();
4256 stderr_w.writeAll("referenced transitive analysis errors, but none actually emitted\n") catch break :print_ctx;
4257 stderr_w.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}) catch break :print_ctx;
4271 const stderr = std.debug.lockStderr(&.{}).terminal();
4272 defer std.debug.unlockStderr();
4273 const w = stderr.writer;
4274 w.writeAll("referenced transitive analysis errors, but none actually emitted\n") catch break :print_ctx;
4275 w.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}) catch break :print_ctx;
42584276 while (ref) |r| {
4259 stderr_w.print("referenced by: {f}{s}\n", .{
4277 w.print("referenced by: {f}{s}\n", .{
42604278 zcu.fmtAnalUnit(r.referencer),
42614279 if (zcu.transitive_failed_analysis.contains(r.referencer)) " [transitive failure]" else "",
42624280 }) catch break :print_ctx;
......@@ -5038,7 +5056,9 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node
50385056 }
50395057
50405058 prelink_group.wait(io);
5041 comp.link_queue.finishPrelinkQueue(comp);
5059 comp.link_queue.finishPrelinkQueue(comp) catch |err| switch (err) {
5060 error.Canceled => return,
5061 };
50425062}
50435063
50445064const JobError = Allocator.Error || Io.Cancelable;
......@@ -5211,13 +5231,10 @@ fn processOneJob(
52115231 }
52125232}
52135233
5214fn createDepFile(
5215 comp: *Compilation,
5216 depfile: []const u8,
5217 binfile: Cache.Path,
5218) anyerror!void {
5234fn createDepFile(comp: *Compilation, depfile: []const u8, binfile: Cache.Path) anyerror!void {
5235 const io = comp.io;
52195236 var buf: [4096]u8 = undefined;
5220 var af = try std.fs.cwd().atomicFile(depfile, .{ .write_buffer = &buf });
5237 var af = try Io.Dir.cwd().atomicFile(io, depfile, .{ .write_buffer = &buf });
52215238 defer af.deinit();
52225239
52235240 comp.writeDepFile(binfile, &af.file_writer.interface) catch return af.file_writer.err.?;
......@@ -5258,39 +5275,35 @@ fn workerDocsCopy(comp: *Compilation) void {
52585275
52595276fn docsCopyFallible(comp: *Compilation) anyerror!void {
52605277 const zcu = comp.zcu orelse return comp.lockAndSetMiscFailure(.docs_copy, "no Zig code to document", .{});
5278 const io = comp.io;
52615279
52625280 const docs_path = comp.resolveEmitPath(comp.emit_docs.?);
5263 var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| {
5281 var out_dir = docs_path.root_dir.handle.createDirPathOpen(io, docs_path.sub_path, .{}) catch |err| {
52645282 return comp.lockAndSetMiscFailure(
52655283 .docs_copy,
52665284 "unable to create output directory '{f}': {s}",
52675285 .{ docs_path, @errorName(err) },
52685286 );
52695287 };
5270 defer out_dir.close();
5288 defer out_dir.close(io);
52715289
52725290 for (&[_][]const u8{ "docs/main.js", "docs/index.html" }) |sub_path| {
52735291 const basename = fs.path.basename(sub_path);
5274 comp.dirs.zig_lib.handle.copyFile(sub_path, out_dir, basename, .{}) catch |err| {
5275 comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {s}", .{
5276 sub_path,
5277 @errorName(err),
5278 });
5279 return;
5280 };
5292 comp.dirs.zig_lib.handle.copyFile(sub_path, out_dir, basename, io, .{}) catch |err|
5293 return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {t}", .{ sub_path, err });
52815294 }
52825295
5283 var tar_file = out_dir.createFile("sources.tar", .{}) catch |err| {
5296 var tar_file = out_dir.createFile(io, "sources.tar", .{}) catch |err| {
52845297 return comp.lockAndSetMiscFailure(
52855298 .docs_copy,
52865299 "unable to create '{f}/sources.tar': {s}",
52875300 .{ docs_path, @errorName(err) },
52885301 );
52895302 };
5290 defer tar_file.close();
5303 defer tar_file.close(io);
52915304
52925305 var buffer: [1024]u8 = undefined;
5293 var tar_file_writer = tar_file.writer(&buffer);
5306 var tar_file_writer = tar_file.writer(io, &buffer);
52945307
52955308 var seen_table: std.AutoArrayHashMapUnmanaged(*Package.Module, []const u8) = .empty;
52965309 defer seen_table.deinit(comp.gpa);
......@@ -5321,17 +5334,17 @@ fn docsCopyModule(
53215334 comp: *Compilation,
53225335 module: *Package.Module,
53235336 name: []const u8,
5324 tar_file_writer: *fs.File.Writer,
5337 tar_file_writer: *Io.File.Writer,
53255338) !void {
53265339 const io = comp.io;
53275340 const root = module.root;
53285341 var mod_dir = d: {
53295342 const root_dir, const sub_path = root.openInfo(comp.dirs);
5330 break :d root_dir.openDir(sub_path, .{ .iterate = true });
5343 break :d root_dir.openDir(io, sub_path, .{ .iterate = true });
53315344 } catch |err| {
53325345 return comp.lockAndSetMiscFailure(.docs_copy, "unable to open directory '{f}': {t}", .{ root.fmt(comp), err });
53335346 };
5334 defer mod_dir.close();
5347 defer mod_dir.close(io);
53355348
53365349 var walker = try mod_dir.walk(comp.gpa);
53375350 defer walker.deinit();
......@@ -5341,7 +5354,7 @@ fn docsCopyModule(
53415354
53425355 var buffer: [1024]u8 = undefined;
53435356
5344 while (try walker.next()) |entry| {
5357 while (try walker.next(io)) |entry| {
53455358 switch (entry.kind) {
53465359 .file => {
53475360 if (!std.mem.endsWith(u8, entry.basename, ".zig")) continue;
......@@ -5350,14 +5363,14 @@ fn docsCopyModule(
53505363 },
53515364 else => continue,
53525365 }
5353 var file = mod_dir.openFile(entry.path, .{}) catch |err| {
5366 var file = mod_dir.openFile(io, entry.path, .{}) catch |err| {
53545367 return comp.lockAndSetMiscFailure(.docs_copy, "unable to open {f}{s}: {t}", .{
53555368 root.fmt(comp), entry.path, err,
53565369 });
53575370 };
5358 defer file.close();
5359 const stat = try file.stat();
5360 var file_reader: fs.File.Reader = .initSize(file.adaptToNewApi(), io, &buffer, stat.size);
5371 defer file.close(io);
5372 const stat = try file.stat(io);
5373 var file_reader: Io.File.Reader = .initSize(file, io, &buffer, stat.size);
53615374
53625375 archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime) catch |err| {
53635376 return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive {f}{s}: {t}", .{
......@@ -5496,13 +5509,13 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU
54965509 try comp.updateSubCompilation(sub_compilation, .docs_wasm, prog_node);
54975510
54985511 var crt_file = try sub_compilation.toCrtFile();
5499 defer crt_file.deinit(gpa);
5512 defer crt_file.deinit(gpa, io);
55005513
55015514 const docs_bin_file = crt_file.full_object_path;
55025515 assert(docs_bin_file.sub_path.len > 0); // emitted binary is not a directory
55035516
55045517 const docs_path = comp.resolveEmitPath(comp.emit_docs.?);
5505 var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| {
5518 var out_dir = docs_path.root_dir.handle.createDirPathOpen(io, docs_path.sub_path, .{}) catch |err| {
55065519 comp.lockAndSetMiscFailure(
55075520 .docs_copy,
55085521 "unable to create output directory '{f}': {t}",
......@@ -5510,12 +5523,14 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU
55105523 );
55115524 return error.AlreadyReported;
55125525 };
5513 defer out_dir.close();
5526 defer out_dir.close(io);
55145527
5515 crt_file.full_object_path.root_dir.handle.copyFile(
5528 Io.Dir.copyFile(
5529 crt_file.full_object_path.root_dir.handle,
55165530 crt_file.full_object_path.sub_path,
55175531 out_dir,
55185532 "main.wasm",
5533 io,
55195534 .{},
55205535 ) catch |err| {
55215536 comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{f}' to '{f}': {t}", .{
......@@ -5692,8 +5707,8 @@ pub fn translateC(
56925707 const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));
56935708 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;
56945709 const cache_dir = comp.dirs.local_cache.handle;
5695 var cache_tmp_dir = try cache_dir.makeOpenPath(tmp_sub_path, .{});
5696 defer cache_tmp_dir.close();
5710 var cache_tmp_dir = try cache_dir.createDirPathOpen(io, tmp_sub_path, .{});
5711 defer cache_tmp_dir.close(io);
56975712
56985713 const translated_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, translated_basename });
56995714 const source_path = switch (source) {
......@@ -5702,7 +5717,7 @@ pub fn translateC(
57025717 const out_h_sub_path = tmp_sub_path ++ fs.path.sep_str ++ cimport_basename;
57035718 const out_h_path = try comp.dirs.local_cache.join(arena, &.{out_h_sub_path});
57045719 if (comp.verbose_cimport) log.info("writing C import source to {s}", .{out_h_path});
5705 try cache_dir.writeFile(.{ .sub_path = out_h_sub_path, .data = c_src });
5720 try cache_dir.writeFile(io, .{ .sub_path = out_h_sub_path, .data = c_src });
57065721 break :path out_h_path;
57075722 },
57085723 .path => |p| p,
......@@ -5749,7 +5764,7 @@ pub fn translateC(
57495764 try argv.appendSlice(comp.global_cc_argv);
57505765 try argv.appendSlice(owner_mod.cc_argv);
57515766 try argv.appendSlice(&.{ source_path, "-o", translated_path });
5752 if (comp.verbose_cimport) dump_argv(argv.items);
5767 if (comp.verbose_cimport) try dumpArgv(io, argv.items);
57535768 }
57545769
57555770 var stdout: []u8 = undefined;
......@@ -5775,7 +5790,7 @@ pub fn translateC(
57755790 }
57765791
57775792 // Just to save disk space, we delete the file because it is never needed again.
5778 cache_tmp_dir.deleteFile(dep_basename) catch |err| {
5793 cache_tmp_dir.deleteFile(io, dep_basename) catch |err| {
57795794 log.warn("failed to delete '{s}': {t}", .{ dep_file_path, err });
57805795 };
57815796 }
......@@ -5805,7 +5820,7 @@ pub fn translateC(
58055820 const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest;
58065821
58075822 if (comp.verbose_cimport) log.info("renaming {s} to {s}", .{ tmp_sub_path, o_sub_path });
5808 try renameTmpIntoCache(comp.dirs.local_cache, tmp_sub_path, o_sub_path);
5823 try renameTmpIntoCache(io, comp.dirs.local_cache, tmp_sub_path, o_sub_path);
58095824
58105825 return .{
58115826 .digest = bin_digest,
......@@ -6144,7 +6159,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
61446159 const gpa = comp.gpa;
61456160 const io = comp.io;
61466161
6147 if (c_object.clearStatus(gpa)) {
6162 if (c_object.clearStatus(gpa, io)) {
61486163 // There was previous failure.
61496164 comp.mutex.lockUncancelable(io);
61506165 defer comp.mutex.unlock(io);
......@@ -6257,7 +6272,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
62576272 }
62586273
62596274 if (comp.verbose_cc) {
6260 dump_argv(argv.items);
6275 try dumpArgv(io, argv.items);
62616276 }
62626277
62636278 const err = std.process.execv(arena, argv.items);
......@@ -6267,8 +6282,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
62676282 // We can't know the digest until we do the C compiler invocation,
62686283 // so we need a temporary filename.
62696284 const out_obj_path = try comp.tmpFilePath(arena, o_basename);
6270 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{});
6271 defer zig_cache_tmp_dir.close();
6285 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{});
6286 defer zig_cache_tmp_dir.close(io);
62726287
62736288 const out_diag_path = if (comp.clang_passthrough_mode or !ext.clangSupportsDiagnostics())
62746289 null
......@@ -6303,15 +6318,15 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
63036318 }
63046319
63056320 if (comp.verbose_cc) {
6306 dump_argv(argv.items);
6321 try dumpArgv(io, argv.items);
63076322 }
63086323
63096324 // Just to save disk space, we delete the files that are never needed again.
6310 defer if (out_diag_path) |diag_file_path| zig_cache_tmp_dir.deleteFile(fs.path.basename(diag_file_path)) catch |err| switch (err) {
6325 defer if (out_diag_path) |diag_file_path| zig_cache_tmp_dir.deleteFile(io, fs.path.basename(diag_file_path)) catch |err| switch (err) {
63116326 error.FileNotFound => {}, // the file wasn't created due to an error we reported
63126327 else => log.warn("failed to delete '{s}': {s}", .{ diag_file_path, @errorName(err) }),
63136328 };
6314 defer if (out_dep_path) |dep_file_path| zig_cache_tmp_dir.deleteFile(fs.path.basename(dep_file_path)) catch |err| switch (err) {
6329 defer if (out_dep_path) |dep_file_path| zig_cache_tmp_dir.deleteFile(io, fs.path.basename(dep_file_path)) catch |err| switch (err) {
63156330 error.FileNotFound => {}, // the file wasn't created due to an error we reported
63166331 else => log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }),
63176332 };
......@@ -6322,7 +6337,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
63226337 child.stdout_behavior = .Inherit;
63236338 child.stderr_behavior = .Inherit;
63246339
6325 const term = child.spawnAndWait() catch |err| {
6340 const term = child.spawnAndWait(io) catch |err| {
63266341 return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {s}", .{ argv.items[0], @errorName(err) });
63276342 };
63286343 switch (term) {
......@@ -6340,12 +6355,12 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
63406355 child.stdout_behavior = .Ignore;
63416356 child.stderr_behavior = .Pipe;
63426357
6343 try child.spawn();
6358 try child.spawn(io);
63446359
63456360 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
63466361 const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));
63476362
6348 const term = child.wait() catch |err| {
6363 const term = child.wait(io) catch |err| {
63496364 return comp.failCObj(c_object, "failed to spawn zig clang {s}: {s}", .{ argv.items[0], @errorName(err) });
63506365 };
63516366
......@@ -6387,7 +6402,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
63876402
63886403 if (comp.file_system_inputs != null) {
63896404 // Use the same file size limit as the cache code does for dependency files.
6390 const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(dep_basename, gpa, .limited(Cache.manifest_file_size_max));
6405 const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(io, dep_basename, gpa, .limited(Cache.manifest_file_size_max));
63916406 defer gpa.free(dep_file_contents);
63926407
63936408 var str_buf: std.ArrayList(u8) = .empty;
......@@ -6432,10 +6447,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
64326447 // Rename into place.
64336448 const digest = man.final();
64346449 const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest });
6435 var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{});
6436 defer o_dir.close();
6450 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
6451 defer o_dir.close(io);
64376452 const tmp_basename = fs.path.basename(out_obj_path);
6438 try fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename);
6453 try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename, io);
64396454 break :blk digest;
64406455 };
64416456
......@@ -6477,8 +6492,6 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
64776492 const tracy_trace = trace(@src());
64786493 defer tracy_trace.end();
64796494
6480 const io = comp.io;
6481
64826495 const src_path = switch (win32_resource.src) {
64836496 .rc => |rc_src| rc_src.src_path,
64846497 .manifest => |src_path| src_path,
......@@ -6487,11 +6500,13 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
64876500
64886501 log.debug("updating win32 resource: {s}", .{src_path});
64896502
6503 const io = comp.io;
6504
64906505 var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
64916506 defer arena_allocator.deinit();
64926507 const arena = arena_allocator.allocator();
64936508
6494 if (win32_resource.clearStatus(comp.gpa)) {
6509 if (win32_resource.clearStatus(comp.gpa, io)) {
64956510 // There was previous failure.
64966511 comp.mutex.lockUncancelable(io);
64976512 defer comp.mutex.unlock(io);
......@@ -6521,8 +6536,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
65216536 const digest = man.final();
65226537
65236538 const o_sub_path = try fs.path.join(arena, &.{ "o", &digest });
6524 var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{});
6525 defer o_dir.close();
6539 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
6540 defer o_dir.close(io);
65266541
65276542 const in_rc_path = try comp.dirs.local_cache.join(comp.gpa, &.{
65286543 o_sub_path, rc_basename,
......@@ -6559,7 +6574,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
65596574 resource_id, resource_type, fmtRcEscape(src_path),
65606575 });
65616576
6562 try o_dir.writeFile(.{ .sub_path = rc_basename, .data = input });
6577 try o_dir.writeFile(io, .{ .sub_path = rc_basename, .data = input });
65636578
65646579 var argv = std.array_list.Managed([]const u8).init(comp.gpa);
65656580 defer argv.deinit();
......@@ -6609,8 +6624,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
66096624 const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len];
66106625
66116626 const digest = if (try man.hit()) man.final() else blk: {
6612 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{});
6613 defer zig_cache_tmp_dir.close();
6627 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{});
6628 defer zig_cache_tmp_dir.close(io);
66146629
66156630 const res_filename = try std.fmt.allocPrint(arena, "{s}.res", .{rc_basename_noext});
66166631
......@@ -6652,7 +6667,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
66526667 // Read depfile and update cache manifest
66536668 {
66546669 const dep_basename = fs.path.basename(out_dep_path);
6655 const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(dep_basename, arena, .limited(50 * 1024 * 1024));
6670 const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(io, dep_basename, arena, .limited(50 * 1024 * 1024));
66566671 defer arena.free(dep_file_contents);
66576672
66586673 const value = try std.json.parseFromSliceLeaky(std.json.Value, arena, dep_file_contents, .{});
......@@ -6680,10 +6695,10 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
66806695 // Rename into place.
66816696 const digest = man.final();
66826697 const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest });
6683 var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{});
6684 defer o_dir.close();
6698 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
6699 defer o_dir.close(io);
66856700 const tmp_basename = fs.path.basename(out_res_path);
6686 try fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename);
6701 try Io.Dir.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename, io);
66876702 break :blk digest;
66886703 };
66896704
......@@ -6716,6 +6731,7 @@ fn spawnZigRc(
67166731 argv: []const []const u8,
67176732 child_progress_node: std.Progress.Node,
67186733) !void {
6734 const io = comp.io;
67196735 var node_name: std.ArrayList(u8) = .empty;
67206736 defer node_name.deinit(arena);
67216737
......@@ -6725,8 +6741,8 @@ fn spawnZigRc(
67256741 child.stderr_behavior = .Pipe;
67266742 child.progress_node = child_progress_node;
67276743
6728 child.spawn() catch |err| {
6729 return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv[0], @errorName(err) });
6744 child.spawn(io) catch |err| {
6745 return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {t}", .{ argv[0], err });
67306746 };
67316747
67326748 var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{
......@@ -6758,7 +6774,7 @@ fn spawnZigRc(
67586774 // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace)
67596775 const stderr = poller.reader(.stderr);
67606776
6761 const term = child.wait() catch |err| {
6777 const term = child.wait(io) catch |err| {
67626778 return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv[0], @errorName(err) });
67636779 };
67646780
......@@ -7765,17 +7781,25 @@ pub fn lockAndSetMiscFailure(
77657781 return setMiscFailure(comp, tag, format, args);
77667782}
77677783
7768pub fn dump_argv(argv: []const []const u8) void {
7784pub fn dumpArgv(io: Io, argv: []const []const u8) Io.Cancelable!void {
77697785 var buffer: [64]u8 = undefined;
7770 const stderr, _ = std.debug.lockStderrWriter(&buffer);
7771 defer std.debug.unlockStderrWriter();
7772 nosuspend {
7773 for (argv, 0..) |arg, i| {
7774 if (i != 0) stderr.writeByte(' ') catch return;
7775 stderr.writeAll(arg) catch return;
7776 }
7777 stderr.writeByte('\n') catch return;
7786 const stderr = try io.lockStderr(&buffer, null);
7787 defer io.unlockStderr();
7788 const w = &stderr.file_writer.interface;
7789 return dumpArgvWriter(w, argv) catch |err| switch (err) {
7790 error.WriteFailed => switch (stderr.file_writer.err.?) {
7791 error.Canceled => return error.Canceled,
7792 else => return,
7793 },
7794 };
7795}
7796
7797fn dumpArgvWriter(w: *Io.Writer, argv: []const []const u8) Io.Writer.Error!void {
7798 for (argv, 0..) |arg, i| {
7799 if (i != 0) try w.writeByte(' ');
7800 try w.writeAll(arg);
77787801 }
7802 try w.writeByte('\n');
77797803}
77807804
77817805pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend {
src/InternPool.zig+30-30
......@@ -1,20 +1,21 @@
11//! All interned objects have both a value and a type.
22//! This data structure is self-contained.
3const InternPool = @This();
34
45const builtin = @import("builtin");
6
57const std = @import("std");
8const Io = std.Io;
69const Allocator = std.mem.Allocator;
710const assert = std.debug.assert;
811const BigIntConst = std.math.big.int.Const;
912const BigIntMutable = std.math.big.int.Mutable;
1013const Cache = std.Build.Cache;
11const Io = std.Io;
1214const Limb = std.math.big.Limb;
1315const Hash = std.hash.Wyhash;
16const Zir = std.zig.Zir;
1417
15const InternPool = @This();
1618const Zcu = @import("Zcu.zig");
17const Zir = std.zig.Zir;
1819
1920/// One item per thread, indexed by `tid`, which is dense and unique per thread.
2021locals: []Local,
......@@ -11166,11 +11167,15 @@ pub fn mutateVarInit(ip: *InternPool, io: Io, index: Index, init_index: Index) v
1116611167}
1116711168
1116811169pub fn dump(ip: *const InternPool) void {
11169 dumpStatsFallible(ip, std.heap.page_allocator) catch return;
11170 dumpAllFallible(ip) catch return;
11170 var buffer: [4096]u8 = undefined;
11171 const stderr = std.debug.lockStderr(&buffer);
11172 defer std.debug.unlockStderr();
11173 const w = &stderr.file_writer.interface;
11174 dumpStatsFallible(ip, w, std.heap.page_allocator) catch return;
11175 dumpAllFallible(ip, w) catch return;
1117111176}
1117211177
11173fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {
11178fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) !void {
1117411179 var items_len: usize = 0;
1117511180 var extra_len: usize = 0;
1117611181 var limbs_len: usize = 0;
......@@ -11423,18 +11428,13 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {
1142311428 };
1142411429 counts.sort(SortContext{ .map = &counts });
1142511430 const len = @min(50, counts.count());
11426 std.debug.print(" top 50 tags:\n", .{});
11431 try w.print(" top 50 tags:\n", .{});
1142711432 for (counts.keys()[0..len], counts.values()[0..len]) |tag, stats| {
11428 std.debug.print(" {s}: {d} occurrences, {d} total bytes\n", .{
11429 @tagName(tag), stats.count, stats.bytes,
11430 });
11433 try w.print(" {t}: {d} occurrences, {d} total bytes\n", .{ tag, stats.count, stats.bytes });
1143111434 }
1143211435}
1143311436
11434fn dumpAllFallible(ip: *const InternPool) anyerror!void {
11435 var buffer: [4096]u8 = undefined;
11436 const stderr_bw, _ = std.debug.lockStderrWriter(&buffer);
11437 defer std.debug.unlockStderrWriter();
11437fn dumpAllFallible(ip: *const InternPool, w: *Io.Writer) anyerror!void {
1143811438 for (ip.locals, 0..) |*local, tid| {
1143911439 const items = local.shared.items.view();
1144011440 for (
......@@ -11443,12 +11443,12 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void {
1144311443 0..,
1144411444 ) |tag, data, index| {
1144511445 const i = Index.Unwrapped.wrap(.{ .tid = @enumFromInt(tid), .index = @intCast(index) }, ip);
11446 try stderr_bw.print("${d} = {s}(", .{ i, @tagName(tag) });
11446 try w.print("${d} = {s}(", .{ i, @tagName(tag) });
1144711447 switch (tag) {
1144811448 .removed => {},
1144911449
11450 .simple_type => try stderr_bw.print("{s}", .{@tagName(@as(SimpleType, @enumFromInt(@intFromEnum(i))))}),
11451 .simple_value => try stderr_bw.print("{s}", .{@tagName(@as(SimpleValue, @enumFromInt(@intFromEnum(i))))}),
11450 .simple_type => try w.print("{s}", .{@tagName(@as(SimpleType, @enumFromInt(@intFromEnum(i))))}),
11451 .simple_value => try w.print("{s}", .{@tagName(@as(SimpleValue, @enumFromInt(@intFromEnum(i))))}),
1145211452
1145311453 .type_int_signed,
1145411454 .type_int_unsigned,
......@@ -11521,23 +11521,27 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void {
1152111521 .func_coerced,
1152211522 .union_value,
1152311523 .memoized_call,
11524 => try stderr_bw.print("{d}", .{data}),
11524 => try w.print("{d}", .{data}),
1152511525
1152611526 .opt_null,
1152711527 .type_slice,
1152811528 .only_possible_value,
11529 => try stderr_bw.print("${d}", .{data}),
11529 => try w.print("${d}", .{data}),
1153011530 }
11531 try stderr_bw.writeAll(")\n");
11531 try w.writeAll(")\n");
1153211532 }
1153311533 }
1153411534}
1153511535
1153611536pub fn dumpGenericInstances(ip: *const InternPool, allocator: Allocator) void {
11537 ip.dumpGenericInstancesFallible(allocator) catch return;
11537 var buffer: [4096]u8 = undefined;
11538 const stderr = std.debug.lockStderr(&buffer);
11539 defer std.debug.unlockStderr();
11540 const w = &stderr.file_writer.interface;
11541 ip.dumpGenericInstancesFallible(allocator, w) catch return;
1153811542}
1153911543
11540pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) anyerror!void {
11544pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator, w: *Io.Writer) !void {
1154111545 var arena_allocator = std.heap.ArenaAllocator.init(allocator);
1154211546 defer arena_allocator.deinit();
1154311547 const arena = arena_allocator.allocator();
......@@ -11564,10 +11568,6 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator)
1156411568 }
1156511569 }
1156611570
11567 var buffer: [4096]u8 = undefined;
11568 const stderr_bw, _ = std.debug.lockStderrWriter(&buffer);
11569 defer std.debug.unlockStderrWriter();
11570
1157111571 const SortContext = struct {
1157211572 values: []std.ArrayList(Index),
1157311573 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
......@@ -11579,19 +11579,19 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator)
1157911579 var it = instances.iterator();
1158011580 while (it.next()) |entry| {
1158111581 const generic_fn_owner_nav = ip.getNav(ip.funcDeclInfo(entry.key_ptr.*).owner_nav);
11582 try stderr_bw.print("{f} ({d}): \n", .{ generic_fn_owner_nav.name.fmt(ip), entry.value_ptr.items.len });
11582 try w.print("{f} ({d}): \n", .{ generic_fn_owner_nav.name.fmt(ip), entry.value_ptr.items.len });
1158311583 for (entry.value_ptr.items) |index| {
1158411584 const unwrapped_index = index.unwrap(ip);
1158511585 const func = ip.extraFuncInstance(unwrapped_index.tid, unwrapped_index.getExtra(ip), unwrapped_index.getData(ip));
1158611586 const owner_nav = ip.getNav(func.owner_nav);
11587 try stderr_bw.print(" {f}: (", .{owner_nav.name.fmt(ip)});
11587 try w.print(" {f}: (", .{owner_nav.name.fmt(ip)});
1158811588 for (func.comptime_args.get(ip)) |arg| {
1158911589 if (arg != .none) {
1159011590 const key = ip.indexToKey(arg);
11591 try stderr_bw.print(" {} ", .{key});
11591 try w.print(" {} ", .{key});
1159211592 }
1159311593 }
11594 try stderr_bw.writeAll(")\n");
11594 try w.writeAll(")\n");
1159511595 }
1159611596 }
1159711597}
src/Package/Fetch.zig+125-121
......@@ -383,14 +383,14 @@ pub fn run(f: *Fetch) RunError!void {
383383 },
384384 .remote => |remote| remote,
385385 .path_or_url => |path_or_url| {
386 if (fs.cwd().openDir(path_or_url, .{ .iterate = true })) |dir| {
386 if (Io.Dir.cwd().openDir(io, path_or_url, .{ .iterate = true })) |dir| {
387387 var resource: Resource = .{ .dir = dir };
388388 return f.runResource(path_or_url, &resource, null);
389389 } else |dir_err| {
390390 var server_header_buffer: [init_resource_buffer_size]u8 = undefined;
391391
392392 const file_err = if (dir_err == error.NotDir) e: {
393 if (fs.cwd().openFile(path_or_url, .{})) |file| {
393 if (Io.Dir.cwd().openFile(io, path_or_url, .{})) |file| {
394394 var resource: Resource = .{ .file = file.reader(io, &server_header_buffer) };
395395 return f.runResource(path_or_url, &resource, null);
396396 } else |err| break :e err;
......@@ -418,7 +418,7 @@ pub fn run(f: *Fetch) RunError!void {
418418 const prefixed_pkg_sub_path = prefixed_pkg_sub_path_buffer[0 .. 2 + hash_slice.len];
419419 const prefix_len: usize = if (f.job_queue.read_only) "p/".len else 0;
420420 const pkg_sub_path = prefixed_pkg_sub_path[prefix_len..];
421 if (cache_root.handle.access(pkg_sub_path, .{})) |_| {
421 if (cache_root.handle.access(io, pkg_sub_path, .{})) |_| {
422422 assert(f.lazy_status != .unavailable);
423423 f.package_root = .{
424424 .root_dir = cache_root,
......@@ -500,12 +500,12 @@ fn runResource(
500500 var tmp_directory: Cache.Directory = .{
501501 .path = tmp_directory_path,
502502 .handle = handle: {
503 const dir = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{
504 .iterate = true,
503 const dir = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{
504 .open_options = .{ .iterate = true },
505505 }) catch |err| {
506506 try eb.addRootErrorMessage(.{
507 .msg = try eb.printString("unable to create temporary directory '{s}': {s}", .{
508 tmp_directory_path, @errorName(err),
507 .msg = try eb.printString("unable to create temporary directory '{s}': {t}", .{
508 tmp_directory_path, err,
509509 }),
510510 });
511511 return error.FetchFailed;
......@@ -513,7 +513,7 @@ fn runResource(
513513 break :handle dir;
514514 },
515515 };
516 defer tmp_directory.handle.close();
516 defer tmp_directory.handle.close(io);
517517
518518 // Fetch and unpack a resource into a temporary directory.
519519 var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory);
......@@ -523,9 +523,9 @@ fn runResource(
523523 // Apply btrfs workaround if needed. Reopen tmp_directory.
524524 if (native_os == .linux and f.job_queue.work_around_btrfs_bug) {
525525 // https://github.com/ziglang/zig/issues/17095
526 pkg_path.root_dir.handle.close();
527 pkg_path.root_dir.handle = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{
528 .iterate = true,
526 pkg_path.root_dir.handle.close(io);
527 pkg_path.root_dir.handle = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{
528 .open_options = .{ .iterate = true },
529529 }) catch @panic("btrfs workaround failed");
530530 }
531531
......@@ -567,7 +567,7 @@ fn runResource(
567567 .root_dir = cache_root,
568568 .sub_path = try std.fmt.allocPrint(arena, "p" ++ s ++ "{s}", .{computed_package_hash.toSlice()}),
569569 };
570 renameTmpIntoCache(cache_root.handle, package_sub_path, f.package_root.sub_path) catch |err| {
570 renameTmpIntoCache(io, cache_root.handle, package_sub_path, f.package_root.sub_path) catch |err| {
571571 const src = try cache_root.join(arena, &.{tmp_dir_sub_path});
572572 const dest = try cache_root.join(arena, &.{f.package_root.sub_path});
573573 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
......@@ -578,7 +578,7 @@ fn runResource(
578578 };
579579 // Remove temporary directory root if not already renamed to global cache.
580580 if (!std.mem.eql(u8, package_sub_path, tmp_dir_sub_path)) {
581 cache_root.handle.deleteDir(tmp_dir_sub_path) catch {};
581 cache_root.handle.deleteDir(io, tmp_dir_sub_path) catch {};
582582 }
583583
584584 // Validate the computed hash against the expected hash. If invalid, this
......@@ -637,8 +637,9 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash {
637637/// `computeHash` gets a free check for the existence of `build.zig`, but when
638638/// not computing a hash, we need to do a syscall to check for it.
639639fn checkBuildFileExistence(f: *Fetch) RunError!void {
640 const io = f.job_queue.io;
640641 const eb = &f.error_bundle;
641 if (f.package_root.access(Package.build_zig_basename, .{})) |_| {
642 if (f.package_root.access(io, Package.build_zig_basename, .{})) |_| {
642643 f.has_build_zig = true;
643644 } else |err| switch (err) {
644645 error.FileNotFound => {},
......@@ -655,9 +656,11 @@ fn checkBuildFileExistence(f: *Fetch) RunError!void {
655656
656657/// This function populates `f.manifest` or leaves it `null`.
657658fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {
659 const io = f.job_queue.io;
658660 const eb = &f.error_bundle;
659661 const arena = f.arena.allocator();
660662 const manifest_bytes = pkg_root.root_dir.handle.readFileAllocOptions(
663 io,
661664 try fs.path.join(arena, &.{ pkg_root.sub_path, Manifest.basename }),
662665 arena,
663666 .limited(Manifest.max_bytes),
......@@ -882,10 +885,10 @@ fn fail(f: *Fetch, msg_tok: std.zig.Ast.TokenIndex, msg_str: u32) RunError {
882885}
883886
884887const Resource = union(enum) {
885 file: fs.File.Reader,
888 file: Io.File.Reader,
886889 http_request: HttpRequest,
887890 git: Git,
888 dir: fs.Dir,
891 dir: Io.Dir,
889892
890893 const Git = struct {
891894 session: git.Session,
......@@ -908,7 +911,7 @@ const Resource = union(enum) {
908911 .git => |*git_resource| {
909912 git_resource.fetch_stream.deinit();
910913 },
911 .dir => |*dir| dir.close(),
914 .dir => |*dir| dir.close(io),
912915 }
913916 resource.* = undefined;
914917 }
......@@ -995,7 +998,7 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u
995998
996999 if (ascii.eqlIgnoreCase(uri.scheme, "file")) {
9971000 const path = try uri.path.toRawMaybeAlloc(arena);
998 const file = f.parent_package_root.openFile(path, .{}) catch |err| {
1001 const file = f.parent_package_root.openFile(io, path, .{}) catch |err| {
9991002 return f.fail(f.location_tok, try eb.printString("unable to open '{f}{s}': {t}", .{
10001003 f.parent_package_root, path, err,
10011004 }));
......@@ -1247,13 +1250,14 @@ fn unpackResource(
12471250 }
12481251}
12491252
1250fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) RunError!UnpackResult {
1253fn unpackTarball(f: *Fetch, out_dir: Io.Dir, reader: *Io.Reader) RunError!UnpackResult {
12511254 const eb = &f.error_bundle;
12521255 const arena = f.arena.allocator();
1256 const io = f.job_queue.io;
12531257
12541258 var diagnostics: std.tar.Diagnostics = .{ .allocator = arena };
12551259
1256 std.tar.pipeToFileSystem(out_dir, reader, .{
1260 std.tar.pipeToFileSystem(io, out_dir, reader, .{
12571261 .diagnostics = &diagnostics,
12581262 .strip_components = 0,
12591263 .mode_mode = .ignore,
......@@ -1280,7 +1284,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) RunError!Unpack
12801284
12811285fn unzip(
12821286 f: *Fetch,
1283 out_dir: fs.Dir,
1287 out_dir: Io.Dir,
12841288 reader: *Io.Reader,
12851289) error{ ReadFailed, OutOfMemory, Canceled, FetchFailed }!UnpackResult {
12861290 // We write the entire contents to a file first because zip files
......@@ -1302,7 +1306,7 @@ fn unzip(
13021306 const random_integer = std.crypto.random.int(u64);
13031307 zip_path[prefix.len..][0..random_len].* = std.fmt.hex(random_integer);
13041308
1305 break cache_root.handle.createFile(&zip_path, .{
1309 break cache_root.handle.createFile(io, &zip_path, .{
13061310 .exclusive = true,
13071311 .read = true,
13081312 }) catch |err| switch (err) {
......@@ -1314,10 +1318,10 @@ fn unzip(
13141318 ),
13151319 };
13161320 };
1317 defer zip_file.close();
1321 defer zip_file.close(io);
13181322 var zip_file_buffer: [4096]u8 = undefined;
13191323 var zip_file_reader = b: {
1320 var zip_file_writer = zip_file.writer(&zip_file_buffer);
1324 var zip_file_writer = zip_file.writer(io, &zip_file_buffer);
13211325
13221326 _ = reader.streamRemaining(&zip_file_writer.interface) catch |err| switch (err) {
13231327 error.ReadFailed => return error.ReadFailed,
......@@ -1330,7 +1334,7 @@ fn unzip(
13301334 f.location_tok,
13311335 try eb.printString("failed writing temporary zip file: {t}", .{err}),
13321336 );
1333 break :b zip_file_writer.moveToReader(io);
1337 break :b zip_file_writer.moveToReader();
13341338 };
13351339
13361340 var diagnostics: std.zip.Diagnostics = .{ .allocator = f.arena.allocator() };
......@@ -1343,13 +1347,13 @@ fn unzip(
13431347 .diagnostics = &diagnostics,
13441348 }) catch |err| return f.fail(f.location_tok, try eb.printString("zip extract failed: {t}", .{err}));
13451349
1346 cache_root.handle.deleteFile(&zip_path) catch |err|
1350 cache_root.handle.deleteFile(io, &zip_path) catch |err|
13471351 return f.fail(f.location_tok, try eb.printString("delete temporary zip failed: {t}", .{err}));
13481352
13491353 return .{ .root_dir = diagnostics.root_dir };
13501354}
13511355
1352fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!UnpackResult {
1356fn unpackGitPack(f: *Fetch, out_dir: Io.Dir, resource: *Resource.Git) anyerror!UnpackResult {
13531357 const io = f.job_queue.io;
13541358 const arena = f.arena.allocator();
13551359 // TODO don't try to get a gpa from an arena. expose this dependency higher up
......@@ -1362,23 +1366,23 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
13621366 // we do not attempt to replicate the exact structure of a real .git
13631367 // directory, since that isn't relevant for fetching a package.
13641368 {
1365 var pack_dir = try out_dir.makeOpenPath(".git", .{});
1366 defer pack_dir.close();
1367 var pack_file = try pack_dir.createFile("pkg.pack", .{ .read = true });
1368 defer pack_file.close();
1369 var pack_dir = try out_dir.createDirPathOpen(io, ".git", .{});
1370 defer pack_dir.close(io);
1371 var pack_file = try pack_dir.createFile(io, "pkg.pack", .{ .read = true });
1372 defer pack_file.close(io);
13691373 var pack_file_buffer: [4096]u8 = undefined;
13701374 var pack_file_reader = b: {
1371 var pack_file_writer = pack_file.writer(&pack_file_buffer);
1375 var pack_file_writer = pack_file.writer(io, &pack_file_buffer);
13721376 const fetch_reader = &resource.fetch_stream.reader;
13731377 _ = try fetch_reader.streamRemaining(&pack_file_writer.interface);
13741378 try pack_file_writer.interface.flush();
1375 break :b pack_file_writer.moveToReader(io);
1379 break :b pack_file_writer.moveToReader();
13761380 };
13771381
1378 var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true });
1379 defer index_file.close();
1382 var index_file = try pack_dir.createFile(io, "pkg.idx", .{ .read = true });
1383 defer index_file.close(io);
13801384 var index_file_buffer: [2000]u8 = undefined;
1381 var index_file_writer = index_file.writer(&index_file_buffer);
1385 var index_file_writer = index_file.writer(io, &index_file_buffer);
13821386 {
13831387 const index_prog_node = f.prog_node.start("Index pack", 0);
13841388 defer index_prog_node.end();
......@@ -1393,7 +1397,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
13931397 try repository.init(gpa, object_format, &pack_file_reader, &index_file_reader);
13941398 defer repository.deinit();
13951399 var diagnostics: git.Diagnostics = .{ .allocator = arena };
1396 try repository.checkout(out_dir, resource.want_oid, &diagnostics);
1400 try repository.checkout(io, out_dir, resource.want_oid, &diagnostics);
13971401
13981402 if (diagnostics.errors.items.len > 0) {
13991403 try res.allocErrors(arena, diagnostics.errors.items.len, "unable to unpack packfile");
......@@ -1407,41 +1411,37 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
14071411 }
14081412 }
14091413
1410 try out_dir.deleteTree(".git");
1414 try out_dir.deleteTree(io, ".git");
14111415 return res;
14121416}
14131417
1414fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void {
1418fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void {
14151419 const gpa = f.arena.child_allocator;
1420 const io = f.job_queue.io;
14161421 // Recursive directory copy.
14171422 var it = try dir.walk(gpa);
14181423 defer it.deinit();
1419 while (try it.next()) |entry| {
1424 while (try it.next(io)) |entry| {
14201425 switch (entry.kind) {
14211426 .directory => {}, // omit empty directories
14221427 .file => {
1423 dir.copyFile(
1424 entry.path,
1425 tmp_dir,
1426 entry.path,
1427 .{},
1428 ) catch |err| switch (err) {
1428 dir.copyFile(entry.path, tmp_dir, entry.path, io, .{}) catch |err| switch (err) {
14291429 error.FileNotFound => {
1430 if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname);
1431 try dir.copyFile(entry.path, tmp_dir, entry.path, .{});
1430 if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.createDirPath(io, dirname);
1431 try dir.copyFile(entry.path, tmp_dir, entry.path, io, .{});
14321432 },
14331433 else => |e| return e,
14341434 };
14351435 },
14361436 .sym_link => {
14371437 var buf: [fs.max_path_bytes]u8 = undefined;
1438 const link_name = try dir.readLink(entry.path, &buf);
1438 const link_name = buf[0..try dir.readLink(io, entry.path, &buf)];
14391439 // TODO: if this would create a symlink to outside
14401440 // the destination directory, fail with an error instead.
1441 tmp_dir.symLink(link_name, entry.path, .{}) catch |err| switch (err) {
1441 tmp_dir.symLink(io, link_name, entry.path, .{}) catch |err| switch (err) {
14421442 error.FileNotFound => {
1443 if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname);
1444 try tmp_dir.symLink(link_name, entry.path, .{});
1443 if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.createDirPath(io, dirname);
1444 try tmp_dir.symLink(io, link_name, entry.path, .{});
14451445 },
14461446 else => |e| return e,
14471447 };
......@@ -1451,14 +1451,14 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void
14511451 }
14521452}
14531453
1454pub fn renameTmpIntoCache(cache_dir: fs.Dir, tmp_dir_sub_path: []const u8, dest_dir_sub_path: []const u8) !void {
1454pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u8, dest_dir_sub_path: []const u8) !void {
14551455 assert(dest_dir_sub_path[1] == fs.path.sep);
14561456 var handled_missing_dir = false;
14571457 while (true) {
1458 cache_dir.rename(tmp_dir_sub_path, dest_dir_sub_path) catch |err| switch (err) {
1458 cache_dir.rename(tmp_dir_sub_path, cache_dir, dest_dir_sub_path, io) catch |err| switch (err) {
14591459 error.FileNotFound => {
14601460 if (handled_missing_dir) return err;
1461 cache_dir.makeDir(dest_dir_sub_path[0..1]) catch |mkd_err| switch (mkd_err) {
1461 cache_dir.createDir(io, dest_dir_sub_path[0..1], .default_dir) catch |mkd_err| switch (mkd_err) {
14621462 error.PathAlreadyExists => handled_missing_dir = true,
14631463 else => |e| return e,
14641464 };
......@@ -1466,7 +1466,7 @@ pub fn renameTmpIntoCache(cache_dir: fs.Dir, tmp_dir_sub_path: []const u8, dest_
14661466 },
14671467 error.PathAlreadyExists, error.AccessDenied => {
14681468 // Package has been already downloaded and may already be in use on the system.
1469 cache_dir.deleteTree(tmp_dir_sub_path) catch {
1469 cache_dir.deleteTree(io, tmp_dir_sub_path) catch {
14701470 // Garbage files leftover in zig-cache/tmp/ is, as they say
14711471 // on Star Trek, "operating within normal parameters".
14721472 };
......@@ -1519,7 +1519,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
15191519 var group: Io.Group = .init;
15201520 defer group.wait(io);
15211521
1522 while (walker.next() catch |err| {
1522 while (walker.next(io) catch |err| {
15231523 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
15241524 "unable to walk temporary directory '{f}': {s}",
15251525 .{ pkg_path, @errorName(err) },
......@@ -1542,7 +1542,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
15421542 .fs_path = fs_path,
15431543 .failure = undefined, // to be populated by the worker
15441544 };
1545 group.async(io, workerDeleteFile, .{ root_dir, deleted_file });
1545 group.async(io, workerDeleteFile, .{ io, root_dir, deleted_file });
15461546 try deleted_files.append(deleted_file);
15471547 continue;
15481548 }
......@@ -1570,7 +1570,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
15701570 .failure = undefined, // to be populated by the worker
15711571 .size = undefined, // to be populated by the worker
15721572 };
1573 group.async(io, workerHashFile, .{ root_dir, hashed_file });
1573 group.async(io, workerHashFile, .{ io, root_dir, hashed_file });
15741574 try all_files.append(hashed_file);
15751575 }
15761576 }
......@@ -1588,7 +1588,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
15881588 var i: usize = 0;
15891589 while (i < sus_dirs.count()) : (i += 1) {
15901590 const sus_dir = sus_dirs.keys()[i];
1591 root_dir.deleteDir(sus_dir) catch |err| switch (err) {
1591 root_dir.deleteDir(io, sus_dir) catch |err| switch (err) {
15921592 error.DirNotEmpty => continue,
15931593 error.FileNotFound => continue,
15941594 else => |e| {
......@@ -1638,7 +1638,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
16381638 assert(!f.job_queue.recursive);
16391639 // Print something to stdout that can be text diffed to figure out why
16401640 // the package hash is different.
1641 dumpHashInfo(all_files.items) catch |err| {
1641 dumpHashInfo(io, all_files.items) catch |err| {
16421642 std.debug.print("unable to write to stdout: {s}\n", .{@errorName(err)});
16431643 std.process.exit(1);
16441644 };
......@@ -1650,9 +1650,9 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
16501650 };
16511651}
16521652
1653fn dumpHashInfo(all_files: []const *const HashedFile) !void {
1653fn dumpHashInfo(io: Io, all_files: []const *const HashedFile) !void {
16541654 var stdout_buffer: [1024]u8 = undefined;
1655 var stdout_writer: fs.File.Writer = .initStreaming(.stdout(), &stdout_buffer);
1655 var stdout_writer: Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer);
16561656 const w = &stdout_writer.interface;
16571657 for (all_files) |hashed_file| {
16581658 try w.print("{t}: {x}: {s}\n", .{ hashed_file.kind, &hashed_file.hash, hashed_file.normalized_path });
......@@ -1660,15 +1660,15 @@ fn dumpHashInfo(all_files: []const *const HashedFile) !void {
16601660 try w.flush();
16611661}
16621662
1663fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile) void {
1664 hashed_file.failure = hashFileFallible(dir, hashed_file);
1663fn workerHashFile(io: Io, dir: Io.Dir, hashed_file: *HashedFile) void {
1664 hashed_file.failure = hashFileFallible(io, dir, hashed_file);
16651665}
16661666
1667fn workerDeleteFile(dir: fs.Dir, deleted_file: *DeletedFile) void {
1668 deleted_file.failure = deleteFileFallible(dir, deleted_file);
1667fn workerDeleteFile(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) void {
1668 deleted_file.failure = deleteFileFallible(io, dir, deleted_file);
16691669}
16701670
1671fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void {
1671fn hashFileFallible(io: Io, dir: Io.Dir, hashed_file: *HashedFile) HashedFile.Error!void {
16721672 var buf: [8000]u8 = undefined;
16731673 var hasher = Package.Hash.Algo.init(.{});
16741674 hasher.update(hashed_file.normalized_path);
......@@ -1676,24 +1676,24 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void
16761676
16771677 switch (hashed_file.kind) {
16781678 .file => {
1679 var file = try dir.openFile(hashed_file.fs_path, .{});
1680 defer file.close();
1679 var file = try dir.openFile(io, hashed_file.fs_path, .{});
1680 defer file.close(io);
16811681 // Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463
16821682 hasher.update(&.{ 0, 0 });
16831683 var file_header: FileHeader = .{};
16841684 while (true) {
1685 const bytes_read = try file.read(&buf);
1685 const bytes_read = try file.readPositional(io, &.{&buf}, file_size);
16861686 if (bytes_read == 0) break;
16871687 file_size += bytes_read;
16881688 hasher.update(buf[0..bytes_read]);
16891689 file_header.update(buf[0..bytes_read]);
16901690 }
16911691 if (file_header.isExecutable()) {
1692 try setExecutable(file);
1692 try setExecutable(io, file);
16931693 }
16941694 },
16951695 .link => {
1696 const link_name = try dir.readLink(hashed_file.fs_path, &buf);
1696 const link_name = buf[0..try dir.readLink(io, hashed_file.fs_path, &buf)];
16971697 if (fs.path.sep != canonical_sep) {
16981698 // Package hashes are intended to be consistent across
16991699 // platforms which means we must normalize path separators
......@@ -1707,16 +1707,13 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void
17071707 hashed_file.size = file_size;
17081708}
17091709
1710fn deleteFileFallible(dir: fs.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void {
1711 try dir.deleteFile(deleted_file.fs_path);
1710fn deleteFileFallible(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void {
1711 try dir.deleteFile(io, deleted_file.fs_path);
17121712}
17131713
1714fn setExecutable(file: fs.File) !void {
1715 if (!std.fs.has_executable_bit) return;
1716
1717 const S = std.posix.S;
1718 const mode = fs.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH;
1719 try file.chmod(mode);
1714fn setExecutable(io: Io, file: Io.File) !void {
1715 if (!Io.File.Permissions.has_executable_bit) return;
1716 try file.setPermissions(io, .executable_file);
17201717}
17211718
17221719const DeletedFile = struct {
......@@ -1724,8 +1721,8 @@ const DeletedFile = struct {
17241721 failure: Error!void,
17251722
17261723 const Error =
1727 fs.Dir.DeleteFileError ||
1728 fs.Dir.DeleteDirError;
1724 Io.Dir.DeleteFileError ||
1725 Io.Dir.DeleteDirError;
17291726};
17301727
17311728const HashedFile = struct {
......@@ -1737,11 +1734,11 @@ const HashedFile = struct {
17371734 size: u64,
17381735
17391736 const Error =
1740 fs.File.OpenError ||
1741 fs.File.ReadError ||
1742 fs.File.StatError ||
1743 fs.File.ChmodError ||
1744 fs.Dir.ReadLinkError;
1737 Io.File.OpenError ||
1738 Io.File.ReadPositionalError ||
1739 Io.File.StatError ||
1740 Io.File.SetPermissionsError ||
1741 Io.Dir.ReadLinkError;
17451742
17461743 const Kind = enum { file, link };
17471744
......@@ -2043,7 +2040,7 @@ const UnpackResult = struct {
20432040 defer errors.deinit(gpa);
20442041 var aw: Io.Writer.Allocating = .init(gpa);
20452042 defer aw.deinit();
2046 try errors.renderToWriter(.{}, &aw.writer, .no_color);
2043 try errors.renderToWriter(.{}, &aw.writer);
20472044 try std.testing.expectEqualStrings(
20482045 \\error: unable to unpack
20492046 \\ note: unable to create symlink from 'dir2/file2' to 'filename': SymlinkError
......@@ -2074,7 +2071,7 @@ test "tarball with duplicate paths" {
20742071 defer tmp.cleanup();
20752072
20762073 const tarball_name = "duplicate_paths.tar.gz";
2077 try saveEmbedFile(tarball_name, tmp.dir);
2074 try saveEmbedFile(io, tarball_name, tmp.dir);
20782075 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
20792076 defer gpa.free(tarball_path);
20802077
......@@ -2107,7 +2104,7 @@ test "tarball with excluded duplicate paths" {
21072104 defer tmp.cleanup();
21082105
21092106 const tarball_name = "duplicate_paths_excluded.tar.gz";
2110 try saveEmbedFile(tarball_name, tmp.dir);
2107 try saveEmbedFile(io, tarball_name, tmp.dir);
21112108 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
21122109 defer gpa.free(tarball_path);
21132110
......@@ -2153,7 +2150,7 @@ test "tarball without root folder" {
21532150 defer tmp.cleanup();
21542151
21552152 const tarball_name = "no_root.tar.gz";
2156 try saveEmbedFile(tarball_name, tmp.dir);
2153 try saveEmbedFile(io, tarball_name, tmp.dir);
21572154 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
21582155 defer gpa.free(tarball_path);
21592156
......@@ -2178,7 +2175,7 @@ test "tarball without root folder" {
21782175}
21792176
21802177test "set executable bit based on file content" {
2181 if (!std.fs.has_executable_bit) return error.SkipZigTest;
2178 if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest;
21822179 const gpa = std.testing.allocator;
21832180 const io = std.testing.io;
21842181
......@@ -2186,7 +2183,7 @@ test "set executable bit based on file content" {
21862183 defer tmp.cleanup();
21872184
21882185 const tarball_name = "executables.tar.gz";
2189 try saveEmbedFile(tarball_name, tmp.dir);
2186 try saveEmbedFile(io, tarball_name, tmp.dir);
21902187 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
21912188 defer gpa.free(tarball_path);
21922189
......@@ -2210,16 +2207,16 @@ test "set executable bit based on file content" {
22102207 );
22112208
22122209 var out = try fb.packageDir();
2213 defer out.close();
2210 defer out.close(io);
22142211 const S = std.posix.S;
22152212 // expect executable bit not set
2216 try std.testing.expect((try out.statFile("file1")).mode & S.IXUSR == 0);
2217 try std.testing.expect((try out.statFile("script_without_shebang")).mode & S.IXUSR == 0);
2213 try std.testing.expect((try out.statFile(io, "file1", .{})).permissions.toMode() & S.IXUSR == 0);
2214 try std.testing.expect((try out.statFile(io, "script_without_shebang", .{})).permissions.toMode() & S.IXUSR == 0);
22182215 // expect executable bit set
2219 try std.testing.expect((try out.statFile("hello")).mode & S.IXUSR != 0);
2220 try std.testing.expect((try out.statFile("script")).mode & S.IXUSR != 0);
2221 try std.testing.expect((try out.statFile("script_with_shebang_without_exec_bit")).mode & S.IXUSR != 0);
2222 try std.testing.expect((try out.statFile("hello_ln")).mode & S.IXUSR != 0);
2216 try std.testing.expect((try out.statFile(io, "hello", .{})).permissions.toMode() & S.IXUSR != 0);
2217 try std.testing.expect((try out.statFile(io, "script", .{})).permissions.toMode() & S.IXUSR != 0);
2218 try std.testing.expect((try out.statFile(io, "script_with_shebang_without_exec_bit", .{})).permissions.toMode() & S.IXUSR != 0);
2219 try std.testing.expect((try out.statFile(io, "hello_ln", .{})).permissions.toMode() & S.IXUSR != 0);
22232220
22242221 //
22252222 // $ ls -al zig-cache/tmp/OCz9ovUcstDjTC_U/zig-global-cache/p/1220fecb4c06a9da8673c87fe8810e15785f1699212f01728eadce094d21effeeef3
......@@ -2231,12 +2228,12 @@ test "set executable bit based on file content" {
22312228 // -rwxrwxr-x 1 17 Apr script_with_shebang_without_exec_bit
22322229}
22332230
2234fn saveEmbedFile(comptime tarball_name: []const u8, dir: fs.Dir) !void {
2231fn saveEmbedFile(io: Io, comptime tarball_name: []const u8, dir: Io.Dir) !void {
22352232 //const tarball_name = "duplicate_paths_excluded.tar.gz";
22362233 const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name);
2237 var tmp_file = try dir.createFile(tarball_name, .{});
2238 defer tmp_file.close();
2239 try tmp_file.writeAll(tarball_content);
2234 var tmp_file = try dir.createFile(io, tarball_name, .{});
2235 defer tmp_file.close(io);
2236 try tmp_file.writeStreamingAll(io, tarball_content);
22402237}
22412238
22422239// Builds Fetch with required dependencies, clears dependencies on deinit().
......@@ -2250,10 +2247,10 @@ const TestFetchBuilder = struct {
22502247 self: *TestFetchBuilder,
22512248 allocator: std.mem.Allocator,
22522249 io: Io,
2253 cache_parent_dir: std.fs.Dir,
2250 cache_parent_dir: std.Io.Dir,
22542251 path_or_url: []const u8,
22552252 ) !*Fetch {
2256 const cache_dir = try cache_parent_dir.makeOpenPath("zig-global-cache", .{});
2253 const cache_dir = try cache_parent_dir.createDirPathOpen(io, "zig-global-cache", .{});
22572254
22582255 self.http_client = .{ .allocator = allocator, .io = io };
22592256 self.global_cache_directory = .{ .handle = cache_dir, .path = null };
......@@ -2301,35 +2298,40 @@ const TestFetchBuilder = struct {
23012298 }
23022299
23032300 fn deinit(self: *TestFetchBuilder) void {
2301 const io = self.job_queue.io;
23042302 self.fetch.deinit();
23052303 self.job_queue.deinit();
23062304 self.fetch.prog_node.end();
2307 self.global_cache_directory.handle.close();
2305 self.global_cache_directory.handle.close(io);
23082306 self.http_client.deinit();
23092307 }
23102308
2311 fn packageDir(self: *TestFetchBuilder) !fs.Dir {
2309 fn packageDir(self: *TestFetchBuilder) !Io.Dir {
2310 const io = self.job_queue.io;
23122311 const root = self.fetch.package_root;
2313 return try root.root_dir.handle.openDir(root.sub_path, .{ .iterate = true });
2312 return try root.root_dir.handle.openDir(io, root.sub_path, .{ .iterate = true });
23142313 }
23152314
23162315 // Test helper, asserts thet package dir constains expected_files.
23172316 // expected_files must be sorted.
23182317 fn expectPackageFiles(self: *TestFetchBuilder, expected_files: []const []const u8) !void {
2318 const io = self.job_queue.io;
2319 const gpa = std.testing.allocator;
2320
23192321 var package_dir = try self.packageDir();
2320 defer package_dir.close();
2322 defer package_dir.close(io);
23212323
23222324 var actual_files: std.ArrayList([]u8) = .empty;
2323 defer actual_files.deinit(std.testing.allocator);
2324 defer for (actual_files.items) |file| std.testing.allocator.free(file);
2325 var walker = try package_dir.walk(std.testing.allocator);
2325 defer actual_files.deinit(gpa);
2326 defer for (actual_files.items) |file| gpa.free(file);
2327 var walker = try package_dir.walk(gpa);
23262328 defer walker.deinit();
2327 while (try walker.next()) |entry| {
2329 while (try walker.next(io)) |entry| {
23282330 if (entry.kind != .file) continue;
2329 const path = try std.testing.allocator.dupe(u8, entry.path);
2330 errdefer std.testing.allocator.free(path);
2331 const path = try gpa.dupe(u8, entry.path);
2332 errdefer gpa.free(path);
23312333 std.mem.replaceScalar(u8, path, std.fs.path.sep, '/');
2332 try actual_files.append(std.testing.allocator, path);
2334 try actual_files.append(gpa, path);
23332335 }
23342336 std.mem.sortUnstable([]u8, actual_files.items, {}, struct {
23352337 fn lessThan(_: void, a: []u8, b: []u8) bool {
......@@ -2346,17 +2348,19 @@ const TestFetchBuilder = struct {
23462348
23472349 // Test helper, asserts that fetch has failed with `msg` error message.
23482350 fn expectFetchErrors(self: *TestFetchBuilder, notes_len: usize, msg: []const u8) !void {
2351 const gpa = std.testing.allocator;
2352
23492353 var errors = try self.fetch.error_bundle.toOwnedBundle("");
2350 defer errors.deinit(std.testing.allocator);
2354 defer errors.deinit(gpa);
23512355
23522356 const em = errors.getErrorMessage(errors.getMessages()[0]);
23532357 try std.testing.expectEqual(1, em.count);
23542358 if (notes_len > 0) {
23552359 try std.testing.expectEqual(notes_len, em.notes_len);
23562360 }
2357 var aw: Io.Writer.Allocating = .init(std.testing.allocator);
2361 var aw: Io.Writer.Allocating = .init(gpa);
23582362 defer aw.deinit();
2359 try errors.renderToWriter(.{}, &aw.writer, .no_color);
2363 try errors.renderToWriter(.{}, &aw.writer);
23602364 try std.testing.expectEqualStrings(msg, aw.written());
23612365 }
23622366};
src/Package/Fetch/git.zig+46-44
......@@ -198,8 +198,8 @@ pub const Repository = struct {
198198 repo: *Repository,
199199 allocator: Allocator,
200200 format: Oid.Format,
201 pack_file: *std.fs.File.Reader,
202 index_file: *std.fs.File.Reader,
201 pack_file: *Io.File.Reader,
202 index_file: *Io.File.Reader,
203203 ) !void {
204204 repo.* = .{ .odb = undefined };
205205 try repo.odb.init(allocator, format, pack_file, index_file);
......@@ -213,7 +213,8 @@ pub const Repository = struct {
213213 /// Checks out the repository at `commit_oid` to `worktree`.
214214 pub fn checkout(
215215 repository: *Repository,
216 worktree: std.fs.Dir,
216 io: Io,
217 worktree: Io.Dir,
217218 commit_oid: Oid,
218219 diagnostics: *Diagnostics,
219220 ) !void {
......@@ -223,13 +224,14 @@ pub const Repository = struct {
223224 if (commit_object.type != .commit) return error.NotACommit;
224225 break :tree_oid try getCommitTree(repository.odb.format, commit_object.data);
225226 };
226 try repository.checkoutTree(worktree, tree_oid, "", diagnostics);
227 try repository.checkoutTree(io, worktree, tree_oid, "", diagnostics);
227228 }
228229
229230 /// Checks out the tree at `tree_oid` to `worktree`.
230231 fn checkoutTree(
231232 repository: *Repository,
232 dir: std.fs.Dir,
233 io: Io,
234 dir: Io.Dir,
233235 tree_oid: Oid,
234236 current_path: []const u8,
235237 diagnostics: *Diagnostics,
......@@ -251,18 +253,18 @@ pub const Repository = struct {
251253 while (try tree_iter.next()) |entry| {
252254 switch (entry.type) {
253255 .directory => {
254 try dir.makeDir(entry.name);
255 var subdir = try dir.openDir(entry.name, .{});
256 defer subdir.close();
256 try dir.createDir(io, entry.name, .default_dir);
257 var subdir = try dir.openDir(io, entry.name, .{});
258 defer subdir.close(io);
257259 const sub_path = try std.fs.path.join(repository.odb.allocator, &.{ current_path, entry.name });
258260 defer repository.odb.allocator.free(sub_path);
259 try repository.checkoutTree(subdir, entry.oid, sub_path, diagnostics);
261 try repository.checkoutTree(io, subdir, entry.oid, sub_path, diagnostics);
260262 },
261263 .file => {
262264 try repository.odb.seekOid(entry.oid);
263265 const file_object = try repository.odb.readObject();
264266 if (file_object.type != .blob) return error.InvalidFile;
265 var file = dir.createFile(entry.name, .{ .exclusive = true }) catch |e| {
267 var file = dir.createFile(io, entry.name, .{ .exclusive = true }) catch |e| {
266268 const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name });
267269 errdefer diagnostics.allocator.free(file_name);
268270 try diagnostics.errors.append(diagnostics.allocator, .{ .unable_to_create_file = .{
......@@ -271,15 +273,15 @@ pub const Repository = struct {
271273 } });
272274 continue;
273275 };
274 defer file.close();
275 try file.writeAll(file_object.data);
276 defer file.close(io);
277 try file.writePositionalAll(io, file_object.data, 0);
276278 },
277279 .symlink => {
278280 try repository.odb.seekOid(entry.oid);
279281 const symlink_object = try repository.odb.readObject();
280282 if (symlink_object.type != .blob) return error.InvalidFile;
281283 const link_name = symlink_object.data;
282 dir.symLink(link_name, entry.name, .{}) catch |e| {
284 dir.symLink(io, link_name, entry.name, .{}) catch |e| {
283285 const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name });
284286 errdefer diagnostics.allocator.free(file_name);
285287 const link_name_dup = try diagnostics.allocator.dupe(u8, link_name);
......@@ -294,7 +296,7 @@ pub const Repository = struct {
294296 .gitlink => {
295297 // Consistent with git archive behavior, create the directory but
296298 // do nothing else
297 try dir.makeDir(entry.name);
299 try dir.createDir(io, entry.name, .default_dir);
298300 },
299301 }
300302 }
......@@ -370,9 +372,9 @@ pub const Repository = struct {
370372/// [pack-format](https://git-scm.com/docs/pack-format).
371373const Odb = struct {
372374 format: Oid.Format,
373 pack_file: *std.fs.File.Reader,
375 pack_file: *Io.File.Reader,
374376 index_header: IndexHeader,
375 index_file: *std.fs.File.Reader,
377 index_file: *Io.File.Reader,
376378 cache: ObjectCache = .{},
377379 allocator: Allocator,
378380
......@@ -381,8 +383,8 @@ const Odb = struct {
381383 odb: *Odb,
382384 allocator: Allocator,
383385 format: Oid.Format,
384 pack_file: *std.fs.File.Reader,
385 index_file: *std.fs.File.Reader,
386 pack_file: *Io.File.Reader,
387 index_file: *Io.File.Reader,
386388 ) !void {
387389 try pack_file.seekTo(0);
388390 try index_file.seekTo(0);
......@@ -1270,8 +1272,8 @@ const IndexEntry = struct {
12701272pub fn indexPack(
12711273 allocator: Allocator,
12721274 format: Oid.Format,
1273 pack: *std.fs.File.Reader,
1274 index_writer: *std.fs.File.Writer,
1275 pack: *Io.File.Reader,
1276 index_writer: *Io.File.Writer,
12751277) !void {
12761278 try pack.seekTo(0);
12771279
......@@ -1370,7 +1372,7 @@ pub fn indexPack(
13701372fn indexPackFirstPass(
13711373 allocator: Allocator,
13721374 format: Oid.Format,
1373 pack: *std.fs.File.Reader,
1375 pack: *Io.File.Reader,
13741376 index_entries: *std.AutoHashMapUnmanaged(Oid, IndexEntry),
13751377 pending_deltas: *std.ArrayList(IndexEntry),
13761378) !Oid {
......@@ -1423,7 +1425,7 @@ fn indexPackFirstPass(
14231425fn indexPackHashDelta(
14241426 allocator: Allocator,
14251427 format: Oid.Format,
1426 pack: *std.fs.File.Reader,
1428 pack: *Io.File.Reader,
14271429 delta: IndexEntry,
14281430 index_entries: std.AutoHashMapUnmanaged(Oid, IndexEntry),
14291431 cache: *ObjectCache,
......@@ -1475,7 +1477,7 @@ fn indexPackHashDelta(
14751477fn resolveDeltaChain(
14761478 allocator: Allocator,
14771479 format: Oid.Format,
1478 pack: *std.fs.File.Reader,
1480 pack: *Io.File.Reader,
14791481 base_object: Object,
14801482 delta_offsets: []const u64,
14811483 cache: *ObjectCache,
......@@ -1582,17 +1584,17 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u
15821584
15831585 var git_dir = testing.tmpDir(.{});
15841586 defer git_dir.cleanup();
1585 var pack_file = try git_dir.dir.createFile("testrepo.pack", .{ .read = true });
1586 defer pack_file.close();
1587 try pack_file.writeAll(testrepo_pack);
1587 var pack_file = try git_dir.dir.createFile(io, "testrepo.pack", .{ .read = true });
1588 defer pack_file.close(io);
1589 try pack_file.writeStreamingAll(io, testrepo_pack);
15881590
15891591 var pack_file_buffer: [2000]u8 = undefined;
15901592 var pack_file_reader = pack_file.reader(io, &pack_file_buffer);
15911593
1592 var index_file = try git_dir.dir.createFile("testrepo.idx", .{ .read = true });
1593 defer index_file.close();
1594 var index_file = try git_dir.dir.createFile(io, "testrepo.idx", .{ .read = true });
1595 defer index_file.close(io);
15941596 var index_file_buffer: [2000]u8 = undefined;
1595 var index_file_writer = index_file.writer(&index_file_buffer);
1597 var index_file_writer = index_file.writer(io, &index_file_buffer);
15961598 try indexPack(testing.allocator, format, &pack_file_reader, &index_file_writer);
15971599
15981600 // Arbitrary size limit on files read while checking the repository contents
......@@ -1600,7 +1602,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u
16001602 const max_file_size = 8192;
16011603
16021604 if (!skip_checksums) {
1603 const index_file_data = try git_dir.dir.readFileAlloc("testrepo.idx", testing.allocator, .limited(max_file_size));
1605 const index_file_data = try git_dir.dir.readFileAlloc(io, "testrepo.idx", testing.allocator, .limited(max_file_size));
16041606 defer testing.allocator.free(index_file_data);
16051607 // testrepo.idx is generated by Git. The index created by this file should
16061608 // match it exactly. Running `git verify-pack -v testrepo.pack` can verify
......@@ -1621,7 +1623,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u
16211623
16221624 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
16231625 defer diagnostics.deinit();
1624 try repository.checkout(worktree.dir, commit_id, &diagnostics);
1626 try repository.checkout(io, worktree.dir, commit_id, &diagnostics);
16251627 try testing.expect(diagnostics.errors.items.len == 0);
16261628
16271629 const expected_files: []const []const u8 = &.{
......@@ -1646,7 +1648,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u
16461648 defer for (actual_files.items) |file| testing.allocator.free(file);
16471649 var walker = try worktree.dir.walk(testing.allocator);
16481650 defer walker.deinit();
1649 while (try walker.next()) |entry| {
1651 while (try walker.next(io)) |entry| {
16501652 if (entry.kind != .file) continue;
16511653 const path = try testing.allocator.dupe(u8, entry.path);
16521654 errdefer testing.allocator.free(path);
......@@ -1676,7 +1678,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u
16761678 \\revision 19
16771679 \\
16781680 ;
1679 const actual_file_contents = try worktree.dir.readFileAlloc("file", testing.allocator, .limited(max_file_size));
1681 const actual_file_contents = try worktree.dir.readFileAlloc(io, "file", testing.allocator, .limited(max_file_size));
16801682 defer testing.allocator.free(actual_file_contents);
16811683 try testing.expectEqualStrings(expected_file_contents, actual_file_contents);
16821684}
......@@ -1700,7 +1702,7 @@ test "SHA-256 packfile indexing and checkout" {
17001702pub fn main() !void {
17011703 const allocator = std.heap.smp_allocator;
17021704
1703 var threaded: Io.Threaded = .init(allocator);
1705 var threaded: Io.Threaded = .init(allocator, .{});
17041706 defer threaded.deinit();
17051707 const io = threaded.io();
17061708
......@@ -1712,23 +1714,23 @@ pub fn main() !void {
17121714
17131715 const format = std.meta.stringToEnum(Oid.Format, args[1]) orelse return error.InvalidFormat;
17141716
1715 var pack_file = try std.fs.cwd().openFile(args[2], .{});
1716 defer pack_file.close();
1717 var pack_file = try Io.Dir.cwd().openFile(io, args[2], .{});
1718 defer pack_file.close(io);
17171719 var pack_file_buffer: [4096]u8 = undefined;
17181720 var pack_file_reader = pack_file.reader(io, &pack_file_buffer);
17191721
17201722 const commit = try Oid.parse(format, args[3]);
1721 var worktree = try std.fs.cwd().makeOpenPath(args[4], .{});
1722 defer worktree.close();
1723 var worktree = try Io.Dir.cwd().createDirPathOpen(io, args[4], .{});
1724 defer worktree.close(io);
17231725
1724 var git_dir = try worktree.makeOpenPath(".git", .{});
1725 defer git_dir.close();
1726 var git_dir = try worktree.createDirPathOpen(io, ".git", .{});
1727 defer git_dir.close(io);
17261728
17271729 std.debug.print("Starting index...\n", .{});
1728 var index_file = try git_dir.createFile("idx", .{ .read = true });
1729 defer index_file.close();
1730 var index_file = try git_dir.createFile(io, "idx", .{ .read = true });
1731 defer index_file.close(io);
17301732 var index_file_buffer: [4096]u8 = undefined;
1731 var index_file_writer = index_file.writer(&index_file_buffer);
1733 var index_file_writer = index_file.writer(io, &index_file_buffer);
17321734 try indexPack(allocator, format, &pack_file_reader, &index_file_writer);
17331735
17341736 std.debug.print("Starting checkout...\n", .{});
......@@ -1738,7 +1740,7 @@ pub fn main() !void {
17381740 defer repository.deinit();
17391741 var diagnostics: Diagnostics = .{ .allocator = allocator };
17401742 defer diagnostics.deinit();
1741 try repository.checkout(worktree, commit, &diagnostics);
1743 try repository.checkout(io, worktree, commit, &diagnostics);
17421744
17431745 for (diagnostics.errors.items) |err| {
17441746 std.debug.print("Diagnostic: {}\n", .{err});
src/Sema.zig+5-3
......@@ -2668,16 +2668,18 @@ fn failWithTypeMismatch(sema: *Sema, block: *Block, src: LazySrcLoc, expected: T
26682668
26692669pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg) error{ AnalysisFail, OutOfMemory } {
26702670 @branchHint(.cold);
2671 const gpa = sema.gpa;
26722671 const zcu = sema.pt.zcu;
2672 const comp = zcu.comp;
2673 const gpa = comp.gpa;
2674 const io = comp.io;
26732675
2674 if (build_options.enable_debug_extensions and zcu.comp.debug_compile_errors) {
2676 if (build_options.enable_debug_extensions and comp.debug_compile_errors) {
26752677 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
26762678 wip_errors.init(gpa) catch @panic("out of memory");
26772679 Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory");
26782680 std.debug.print("compile error during Sema:\n", .{});
26792681 var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");
2680 error_bundle.renderToStdErr(.{}, .auto);
2682 error_bundle.renderToStderr(io, .{}, .auto) catch @panic("failed to print to stderr");
26812683 std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{});
26822684 }
26832685
src/Zcu.zig+20-17
......@@ -1076,11 +1076,11 @@ pub const File = struct {
10761076
10771077 var f = f: {
10781078 const dir, const sub_path = file.path.openInfo(zcu.comp.dirs);
1079 break :f try dir.openFile(sub_path, .{});
1079 break :f try dir.openFile(io, sub_path, .{});
10801080 };
1081 defer f.close();
1081 defer f.close(io);
10821082
1083 const stat = f.stat() catch |err| switch (err) {
1083 const stat = f.stat(io) catch |err| switch (err) {
10841084 error.Streaming => {
10851085 // Since `file.stat` is populated, this was previously a file stream; since it is
10861086 // now not a file stream, it must have changed.
......@@ -1200,7 +1200,7 @@ pub const EmbedFile = struct {
12001200 /// `.none` means the file was not loaded, so `stat` is undefined.
12011201 val: InternPool.Index,
12021202 /// If this is `null` and `val` is `.none`, the file has never been loaded.
1203 err: ?(std.fs.File.OpenError || std.fs.File.StatError || std.fs.File.ReadError || error{UnexpectedEof}),
1203 err: ?(Io.File.OpenError || Io.File.StatError || Io.File.Reader.Error || error{UnexpectedEof}),
12041204 stat: Cache.File.Stat,
12051205
12061206 pub const Index = enum(u32) {
......@@ -2813,8 +2813,8 @@ pub fn init(zcu: *Zcu, gpa: Allocator, io: Io, thread_count: usize) !void {
28132813
28142814pub fn deinit(zcu: *Zcu) void {
28152815 const comp = zcu.comp;
2816 const gpa = comp.gpa;
28172816 const io = comp.io;
2817 const gpa = zcu.gpa;
28182818 {
28192819 const pt: Zcu.PerThread = .activate(zcu, .main);
28202820 defer pt.deactivate();
......@@ -2835,8 +2835,8 @@ pub fn deinit(zcu: *Zcu) void {
28352835 }
28362836 zcu.embed_table.deinit(gpa);
28372837
2838 zcu.local_zir_cache.handle.close();
2839 zcu.global_zir_cache.handle.close();
2838 zcu.local_zir_cache.handle.close(io);
2839 zcu.global_zir_cache.handle.close(io);
28402840
28412841 for (zcu.failed_analysis.values()) |value| value.destroy(gpa);
28422842 for (zcu.failed_codegen.values()) |value| value.destroy(gpa);
......@@ -2900,7 +2900,7 @@ pub fn deinit(zcu: *Zcu) void {
29002900
29012901 if (zcu.resolved_references) |*r| r.deinit(gpa);
29022902
2903 if (zcu.comp.debugIncremental()) {
2903 if (comp.debugIncremental()) {
29042904 zcu.incremental_debug_state.deinit(gpa);
29052905 }
29062906 }
......@@ -2927,7 +2927,7 @@ comptime {
29272927 }
29282928}
29292929
2930pub fn loadZirCache(gpa: Allocator, io: Io, cache_file: std.fs.File) !Zir {
2930pub fn loadZirCache(gpa: Allocator, io: Io, cache_file: Io.File) !Zir {
29312931 var buffer: [2000]u8 = undefined;
29322932 var file_reader = cache_file.reader(io, &buffer);
29332933 return result: {
......@@ -2986,7 +2986,12 @@ pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_br: *Io.Reader
29862986 return zir;
29872987}
29882988
2989pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.Stat, zir: Zir) (std.fs.File.WriteError || Allocator.Error)!void {
2989pub fn saveZirCache(
2990 gpa: Allocator,
2991 cache_file_writer: *Io.File.Writer,
2992 stat: Io.File.Stat,
2993 zir: Zir,
2994) (Io.File.Writer.Error || Allocator.Error)!void {
29902995 const safety_buffer = if (data_has_safety_tag)
29912996 try gpa.alloc([8]u8, zir.instructions.len)
29922997 else
......@@ -3020,13 +3025,12 @@ pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.S
30203025 zir.string_bytes,
30213026 @ptrCast(zir.extra),
30223027 };
3023 var cache_fw = cache_file.writer(&.{});
3024 cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) {
3025 error.WriteFailed => return cache_fw.err.?,
3028 cache_file_writer.interface.writeVecAll(&vecs) catch |err| switch (err) {
3029 error.WriteFailed => return cache_file_writer.err.?,
30263030 };
30273031}
30283032
3029pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir) std.fs.File.WriteError!void {
3033pub fn saveZoirCache(cache_file_writer: *Io.File.Writer, stat: Io.File.Stat, zoir: Zoir) Io.File.Writer.Error!void {
30303034 const header: Zoir.Header = .{
30313035 .nodes_len = @intCast(zoir.nodes.len),
30323036 .extra_len = @intCast(zoir.extra.len),
......@@ -3050,9 +3054,8 @@ pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir
30503054 @ptrCast(zoir.compile_errors),
30513055 @ptrCast(zoir.error_notes),
30523056 };
3053 var cache_fw = cache_file.writer(&.{});
3054 cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) {
3055 error.WriteFailed => return cache_fw.err.?,
3057 cache_file_writer.interface.writeVecAll(&vecs) catch |err| switch (err) {
3058 error.WriteFailed => return cache_file_writer.err.?,
30563059 };
30573060}
30583061
src/Zcu/PerThread.zig+54-33
......@@ -94,11 +94,11 @@ pub fn updateFile(
9494 // In any case we need to examine the stat of the file to determine the course of action.
9595 var source_file = f: {
9696 const dir, const sub_path = file.path.openInfo(comp.dirs);
97 break :f try dir.openFile(sub_path, .{});
97 break :f try dir.openFile(io, sub_path, .{});
9898 };
99 defer source_file.close();
99 defer source_file.close(io);
100100
101 const stat = try source_file.stat();
101 const stat = try source_file.stat(io);
102102
103103 const want_local_cache = switch (file.path.root) {
104104 .none, .local_cache => true,
......@@ -118,7 +118,7 @@ pub fn updateFile(
118118 const zir_dir = cache_directory.handle;
119119
120120 // Determine whether we need to reload the file from disk and redo parsing and AstGen.
121 var lock: std.fs.File.Lock = switch (file.status) {
121 var lock: Io.File.Lock = switch (file.status) {
122122 .never_loaded, .retryable_failure => lock: {
123123 // First, load the cached ZIR code, if any.
124124 log.debug("AstGen checking cache: {f} (local={}, digest={s})", .{
......@@ -170,7 +170,7 @@ pub fn updateFile(
170170 // version. Likewise if we're working on AstGen and another process asks for
171171 // the cached file, they'll get it.
172172 const cache_file = while (true) {
173 break zir_dir.createFile(&hex_digest, .{
173 break zir_dir.createFile(io, &hex_digest, .{
174174 .read = true,
175175 .truncate = false,
176176 .lock = lock,
......@@ -196,7 +196,7 @@ pub fn updateFile(
196196 cache_directory,
197197 });
198198 }
199 break zir_dir.createFile(&hex_digest, .{
199 break zir_dir.createFile(io, &hex_digest, .{
200200 .read = true,
201201 .truncate = false,
202202 .lock = lock,
......@@ -215,7 +215,7 @@ pub fn updateFile(
215215 else => |e| return e, // Retryable errors are handled at callsite.
216216 };
217217 };
218 defer cache_file.close();
218 defer cache_file.close(io);
219219
220220 // Under `--time-report`, ignore cache hits; do the work anyway for those juicy numbers.
221221 const ignore_hit = comp.time_report != null;
......@@ -238,18 +238,13 @@ pub fn updateFile(
238238 if (builtin.os.tag == .wasi or lock == .exclusive) break true;
239239 // Otherwise, unlock to give someone a chance to get the exclusive lock
240240 // and then upgrade to an exclusive lock.
241 cache_file.unlock();
241 cache_file.unlock(io);
242242 lock = .exclusive;
243 try cache_file.lock(lock);
243 try cache_file.lock(io, lock);
244244 };
245245
246246 if (need_update) {
247 // The cache is definitely stale so delete the contents to avoid an underwrite later.
248 cache_file.setEndPos(0) catch |err| switch (err) {
249 error.FileTooBig => unreachable, // 0 is not too big
250 else => |e| return e,
251 };
252 try cache_file.seekTo(0);
247 var cache_file_writer: Io.File.Writer = .init(cache_file, io, &.{});
253248
254249 if (stat.size > std.math.maxInt(u32))
255250 return error.FileTooBig;
......@@ -278,22 +273,28 @@ pub fn updateFile(
278273 switch (file.getMode()) {
279274 .zig => {
280275 file.zir = try AstGen.generate(gpa, file.tree.?);
281 Zcu.saveZirCache(gpa, cache_file, stat, file.zir.?) catch |err| switch (err) {
276 Zcu.saveZirCache(gpa, &cache_file_writer, stat, file.zir.?) catch |err| switch (err) {
282277 error.OutOfMemory => |e| return e,
283 else => log.warn("unable to write cached ZIR code for {f} to {f}{s}: {s}", .{
284 file.path.fmt(comp), cache_directory, &hex_digest, @errorName(err),
278 else => log.warn("unable to write cached ZIR code for {f} to {f}{s}: {t}", .{
279 file.path.fmt(comp), cache_directory, &hex_digest, err,
285280 }),
286281 };
287282 },
288283 .zon => {
289284 file.zoir = try ZonGen.generate(gpa, file.tree.?, .{});
290 Zcu.saveZoirCache(cache_file, stat, file.zoir.?) catch |err| {
291 log.warn("unable to write cached ZOIR code for {f} to {f}{s}: {s}", .{
292 file.path.fmt(comp), cache_directory, &hex_digest, @errorName(err),
285 Zcu.saveZoirCache(&cache_file_writer, stat, file.zoir.?) catch |err| {
286 log.warn("unable to write cached ZOIR code for {f} to {f}{s}: {t}", .{
287 file.path.fmt(comp), cache_directory, &hex_digest, err,
293288 });
294289 };
295290 },
296291 }
292
293 cache_file_writer.end() catch |err| switch (err) {
294 error.WriteFailed => return cache_file_writer.err.?,
295 else => |e| return e,
296 };
297
297298 if (timer.finish()) |ns_astgen| {
298299 comp.mutex.lockUncancelable(io);
299300 defer comp.mutex.unlock(io);
......@@ -346,8 +347,8 @@ pub fn updateFile(
346347
347348fn loadZirZoirCache(
348349 zcu: *Zcu,
349 cache_file: std.fs.File,
350 stat: std.fs.File.Stat,
350 cache_file: Io.File,
351 stat: Io.File.Stat,
351352 file: *Zcu.File,
352353 comptime mode: Ast.Mode,
353354) !enum { success, invalid, truncated, stale } {
......@@ -2466,11 +2467,11 @@ fn updateEmbedFileInner(
24662467
24672468 var file = f: {
24682469 const dir, const sub_path = ef.path.openInfo(zcu.comp.dirs);
2469 break :f try dir.openFile(sub_path, .{});
2470 break :f try dir.openFile(io, sub_path, .{});
24702471 };
2471 defer file.close();
2472 defer file.close(io);
24722473
2473 const stat: Cache.File.Stat = .fromFs(try file.stat());
2474 const stat: Cache.File.Stat = .fromFs(try file.stat(io));
24742475
24752476 if (ef.val != .none) {
24762477 const old_stat = ef.stat;
......@@ -4524,12 +4525,14 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru
45244525 .stage2_llvm,
45254526 => {},
45264527 },
4528 error.Canceled => |e| return e,
45274529 }
45284530 return error.AlreadyReported;
45294531 };
45304532}
45314533fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) error{
45324534 OutOfMemory,
4535 Canceled,
45334536 CodegenFail,
45344537 NoLinkFile,
45354538 BackendDoesNotProduceMir,
......@@ -4555,12 +4558,16 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e
45554558 null;
45564559 defer if (liveness) |*l| l.deinit(gpa);
45574560
4558 if (build_options.enable_debug_extensions and comp.verbose_air) {
4559 const stderr, _ = std.debug.lockStderrWriter(&.{});
4560 defer std.debug.unlockStderrWriter();
4561 stderr.print("# Begin Function AIR: {f}:\n", .{fqn.fmt(ip)}) catch {};
4562 air.write(stderr, pt, liveness);
4563 stderr.print("# End Function AIR: {f}\n\n", .{fqn.fmt(ip)}) catch {};
4561 if (build_options.enable_debug_extensions and comp.verbose_air) p: {
4562 const io = comp.io;
4563 const stderr = try io.lockStderr(&.{}, null);
4564 defer io.unlockStderr();
4565 printVerboseAir(pt, liveness, fqn, air, &stderr.file_writer.interface) catch |err| switch (err) {
4566 error.WriteFailed => switch (stderr.file_writer.err.?) {
4567 error.Canceled => |e| return e,
4568 else => break :p,
4569 },
4570 };
45644571 }
45654572
45664573 if (std.debug.runtime_safety) verify_liveness: {
......@@ -4575,7 +4582,7 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e
45754582
45764583 verify.verify() catch |err| switch (err) {
45774584 error.OutOfMemory => return error.OutOfMemory,
4578 else => return zcu.codegenFail(nav, "invalid liveness: {s}", .{@errorName(err)}),
4585 else => return zcu.codegenFail(nav, "invalid liveness: {t}", .{err}),
45794586 };
45804587 }
45814588
......@@ -4611,3 +4618,17 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e
46114618 => return zcu.codegenFail(nav, "unable to codegen: {s}", .{@errorName(err)}),
46124619 };
46134620}
4621
4622fn printVerboseAir(
4623 pt: Zcu.PerThread,
4624 liveness: ?Air.Liveness,
4625 fqn: InternPool.NullTerminatedString,
4626 air: *const Air,
4627 w: *Io.Writer,
4628) Io.Writer.Error!void {
4629 const zcu = pt.zcu;
4630 const ip = &zcu.intern_pool;
4631 try w.print("# Begin Function AIR: {f}:\n", .{fqn.fmt(ip)});
4632 try air.write(w, pt, liveness);
4633 try w.print("# End Function AIR: {f}\n\n", .{fqn.fmt(ip)});
4634}
src/codegen/aarch64/Select.zig+6-4
......@@ -11273,15 +11273,17 @@ fn initValueAdvanced(
1127311273 return @enumFromInt(isel.values.items.len);
1127411274}
1127511275pub fn dumpValues(isel: *Select, which: enum { only_referenced, all }) void {
11276 errdefer |err| @panic(@errorName(err));
11277 const stderr, _ = std.debug.lockStderrWriter(&.{});
11278 defer std.debug.unlockStderrWriter();
11279
1128011276 const zcu = isel.pt.zcu;
1128111277 const gpa = zcu.gpa;
1128211278 const ip = &zcu.intern_pool;
1128311279 const nav = ip.getNav(isel.nav_index);
1128411280
11281 errdefer |err| @panic(@errorName(err));
11282
11283 const locked_stderr = std.debug.lockStderr(&.{});
11284 defer std.debug.unlockStderr();
11285 const stderr = &locked_stderr.file_writer.interface;
11286
1128511287 var reverse_live_values: std.AutoArrayHashMapUnmanaged(Value.Index, std.ArrayList(Air.Inst.Index)) = .empty;
1128611288 defer {
1128711289 for (reverse_live_values.values()) |*list| list.deinit(gpa);
src/codegen/llvm.zig+21-17
......@@ -1,19 +1,22 @@
1const std = @import("std");
21const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
35const assert = std.debug.assert;
46const Allocator = std.mem.Allocator;
57const log = std.log.scoped(.codegen);
68const math = std.math;
79const DW = std.dwarf;
8
910const Builder = std.zig.llvm.Builder;
11
12const build_options = @import("build_options");
1013const llvm = if (build_options.have_llvm)
1114 @import("llvm/bindings.zig")
1215else
1316 @compileError("LLVM unavailable");
17
1418const link = @import("../link.zig");
1519const Compilation = @import("../Compilation.zig");
16const build_options = @import("build_options");
1720const Zcu = @import("../Zcu.zig");
1821const InternPool = @import("../InternPool.zig");
1922const Package = @import("../Package.zig");
......@@ -799,6 +802,7 @@ pub const Object = struct {
799802 pub fn emit(o: *Object, pt: Zcu.PerThread, options: EmitOptions) error{ LinkFailure, OutOfMemory }!void {
800803 const zcu = pt.zcu;
801804 const comp = zcu.comp;
805 const io = comp.io;
802806 const diags = &comp.link_diags;
803807
804808 {
......@@ -961,10 +965,10 @@ pub const Object = struct {
961965 const context, const module = emit: {
962966 if (options.pre_ir_path) |path| {
963967 if (std.mem.eql(u8, path, "-")) {
964 o.builder.dump();
968 o.builder.dump(io);
965969 } else {
966 o.builder.printToFilePath(std.fs.cwd(), path) catch |err| {
967 log.err("failed printing LLVM module to \"{s}\": {s}", .{ path, @errorName(err) });
970 o.builder.printToFilePath(io, Io.Dir.cwd(), path) catch |err| {
971 log.err("failed printing LLVM module to \"{s}\": {t}", .{ path, err });
968972 };
969973 }
970974 }
......@@ -977,26 +981,26 @@ pub const Object = struct {
977981 o.builder.clearAndFree();
978982
979983 if (options.pre_bc_path) |path| {
980 var file = std.fs.cwd().createFile(path, .{}) catch |err|
981 return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) });
982 defer file.close();
984 var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err|
985 return diags.fail("failed to create '{s}': {t}", .{ path, err });
986 defer file.close(io);
983987
984988 const ptr: [*]const u8 = @ptrCast(bitcode.ptr);
985 file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err|
986 return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) });
989 file.writeStreamingAll(io, ptr[0..(bitcode.len * 4)]) catch |err|
990 return diags.fail("failed to write to '{s}': {t}", .{ path, err });
987991 }
988992
989993 if (options.asm_path == null and options.bin_path == null and
990994 options.post_ir_path == null and options.post_bc_path == null) return;
991995
992996 if (options.post_bc_path) |path| {
993 var file = std.fs.cwd().createFile(path, .{}) catch |err|
994 return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) });
995 defer file.close();
997 var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err|
998 return diags.fail("failed to create '{s}': {t}", .{ path, err });
999 defer file.close(io);
9961000
9971001 const ptr: [*]const u8 = @ptrCast(bitcode.ptr);
998 file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err|
999 return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) });
1002 file.writeStreamingAll(io, ptr[0..(bitcode.len * 4)]) catch |err|
1003 return diags.fail("failed to write to '{s}': {t}", .{ path, err });
10001004 }
10011005
10021006 if (!build_options.have_llvm or !comp.config.use_lib_llvm) {
......@@ -2710,7 +2714,7 @@ pub const Object = struct {
27102714 }
27112715
27122716 fn allocTypeName(o: *Object, pt: Zcu.PerThread, ty: Type) Allocator.Error![:0]const u8 {
2713 var aw: std.Io.Writer.Allocating = .init(o.gpa);
2717 var aw: Io.Writer.Allocating = .init(o.gpa);
27142718 defer aw.deinit();
27152719 ty.print(&aw.writer, pt, null) catch |err| switch (err) {
27162720 error.WriteFailed => return error.OutOfMemory,
src/crash_report.zig+7-6
......@@ -95,19 +95,20 @@ fn dumpCrashContext() Io.Writer.Error!void {
9595
9696 // TODO: this does mean that a different thread could grab the stderr mutex between the context
9797 // and the actual panic printing, which would be quite confusing.
98 const stderr, _ = std.debug.lockStderrWriter(&.{});
99 defer std.debug.unlockStderrWriter();
98 const stderr = std.debug.lockStderr(&.{});
99 defer std.debug.unlockStderr();
100 const w = &stderr.file_writer.interface;
100101
101 try stderr.writeAll("Compiler crash context:\n");
102 try w.writeAll("Compiler crash context:\n");
102103
103104 if (CodegenFunc.current) |*cg| {
104105 const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav;
105106 const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn;
106 try stderr.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)});
107 try w.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)});
107108 } else if (AnalyzeBody.current) |anal| {
108 try dumpCrashContextSema(anal, stderr, &S.crash_heap);
109 try dumpCrashContextSema(anal, w, &S.crash_heap);
109110 } else {
110 try stderr.writeAll("(no context)\n\n");
111 try w.writeAll("(no context)\n\n");
111112 }
112113}
113114fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void {
src/fmt.zig+36-34
......@@ -37,9 +37,9 @@ const Fmt = struct {
3737 arena: Allocator,
3838 io: Io,
3939 out_buffer: std.Io.Writer.Allocating,
40 stdout_writer: *fs.File.Writer,
40 stdout_writer: *Io.File.Writer,
4141
42 const SeenMap = std.AutoHashMap(fs.File.INode, void);
42 const SeenMap = std.AutoHashMap(Io.File.INode, void);
4343};
4444
4545pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void {
......@@ -59,8 +59,8 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
5959 const arg = args[i];
6060 if (mem.startsWith(u8, arg, "-")) {
6161 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
62 try fs.File.stdout().writeAll(usage_fmt);
63 return process.cleanExit();
62 try Io.File.stdout().writeStreamingAll(io, usage_fmt);
63 return process.cleanExit(io);
6464 } else if (mem.eql(u8, arg, "--color")) {
6565 if (i + 1 >= args.len) {
6666 fatal("expected [auto|on|off] after --color", .{});
......@@ -99,9 +99,9 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
9999 fatal("cannot use --stdin with positional arguments", .{});
100100 }
101101
102 const stdin: fs.File = .stdin();
102 const stdin: Io.File = .stdin();
103103 var stdio_buffer: [1024]u8 = undefined;
104 var file_reader: fs.File.Reader = stdin.reader(io, &stdio_buffer);
104 var file_reader: Io.File.Reader = stdin.reader(io, &stdio_buffer);
105105 const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| {
106106 fatal("unable to read stdin: {}", .{err});
107107 };
......@@ -124,7 +124,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
124124 try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>");
125125 var error_bundle = try wip_errors.toOwnedBundle("");
126126 defer error_bundle.deinit(gpa);
127 error_bundle.renderToStdErr(.{}, color);
127 error_bundle.renderToStderr(io, .{}, color) catch {};
128128 process.exit(2);
129129 }
130130 } else {
......@@ -138,12 +138,12 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
138138 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, "<stdin>");
139139 var error_bundle = try wip_errors.toOwnedBundle("");
140140 defer error_bundle.deinit(gpa);
141 error_bundle.renderToStdErr(.{}, color);
141 error_bundle.renderToStderr(io, .{}, color) catch {};
142142 process.exit(2);
143143 }
144144 }
145145 } else if (tree.errors.len != 0) {
146 try std.zig.printAstErrorsToStderr(gpa, tree, "<stdin>", color);
146 std.zig.printAstErrorsToStderr(gpa, io, tree, "<stdin>", color) catch {};
147147 process.exit(2);
148148 }
149149 const formatted = try tree.renderAlloc(gpa);
......@@ -154,7 +154,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
154154 process.exit(code);
155155 }
156156
157 return fs.File.stdout().writeAll(formatted);
157 return Io.File.stdout().writeStreamingAll(io, formatted);
158158 }
159159
160160 if (input_files.items.len == 0) {
......@@ -162,7 +162,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
162162 }
163163
164164 var stdout_buffer: [4096]u8 = undefined;
165 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
165 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
166166
167167 var fmt: Fmt = .{
168168 .gpa = gpa,
......@@ -182,13 +182,13 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
182182 // Mark any excluded files/directories as already seen,
183183 // so that they are skipped later during actual processing
184184 for (excluded_files.items) |file_path| {
185 const stat = fs.cwd().statFile(file_path) catch |err| switch (err) {
185 const stat = Io.Dir.cwd().statFile(io, file_path, .{}) catch |err| switch (err) {
186186 error.FileNotFound => continue,
187187 // On Windows, statFile does not work for directories
188188 error.IsDir => dir: {
189 var dir = try fs.cwd().openDir(file_path, .{});
190 defer dir.close();
191 break :dir try dir.stat();
189 var dir = try Io.Dir.cwd().openDir(io, file_path, .{});
190 defer dir.close(io);
191 break :dir try dir.stat(io);
192192 },
193193 else => |e| return e,
194194 };
......@@ -196,7 +196,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
196196 }
197197
198198 for (input_files.items) |file_path| {
199 try fmtPath(&fmt, file_path, check_flag, fs.cwd(), file_path);
199 try fmtPath(&fmt, file_path, check_flag, Io.Dir.cwd(), file_path);
200200 }
201201 try fmt.stdout_writer.interface.flush();
202202 if (fmt.any_error) {
......@@ -204,7 +204,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
204204 }
205205}
206206
207fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) !void {
207fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: Io.Dir, sub_path: []const u8) !void {
208208 fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {
209209 error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),
210210 else => {
......@@ -219,17 +219,19 @@ fn fmtPathDir(
219219 fmt: *Fmt,
220220 file_path: []const u8,
221221 check_mode: bool,
222 parent_dir: fs.Dir,
222 parent_dir: Io.Dir,
223223 parent_sub_path: []const u8,
224224) !void {
225 var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true });
226 defer dir.close();
225 const io = fmt.io;
226
227 var dir = try parent_dir.openDir(io, parent_sub_path, .{ .iterate = true });
228 defer dir.close(io);
227229
228 const stat = try dir.stat();
230 const stat = try dir.stat(io);
229231 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
230232
231233 var dir_it = dir.iterate();
232 while (try dir_it.next()) |entry| {
234 while (try dir_it.next(io)) |entry| {
233235 const is_dir = entry.kind == .directory;
234236
235237 if (mem.startsWith(u8, entry.name, ".")) continue;
......@@ -242,7 +244,7 @@ fn fmtPathDir(
242244 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
243245 } else {
244246 fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {
245 std.log.err("unable to format '{s}': {s}", .{ full_path, @errorName(err) });
247 std.log.err("unable to format '{s}': {t}", .{ full_path, err });
246248 fmt.any_error = true;
247249 return;
248250 };
......@@ -255,22 +257,22 @@ fn fmtPathFile(
255257 fmt: *Fmt,
256258 file_path: []const u8,
257259 check_mode: bool,
258 dir: fs.Dir,
260 dir: Io.Dir,
259261 sub_path: []const u8,
260262) !void {
261263 const io = fmt.io;
262264
263 const source_file = try dir.openFile(sub_path, .{});
265 const source_file = try dir.openFile(io, sub_path, .{});
264266 var file_closed = false;
265 errdefer if (!file_closed) source_file.close();
267 errdefer if (!file_closed) source_file.close(io);
266268
267 const stat = try source_file.stat();
269 const stat = try source_file.stat(io);
268270
269271 if (stat.kind == .directory)
270272 return error.IsDir;
271273
272274 var read_buffer: [1024]u8 = undefined;
273 var file_reader: fs.File.Reader = source_file.reader(io, &read_buffer);
275 var file_reader: Io.File.Reader = source_file.reader(io, &read_buffer);
274276 file_reader.size = stat.size;
275277
276278 const gpa = fmt.gpa;
......@@ -280,7 +282,7 @@ fn fmtPathFile(
280282 };
281283 defer gpa.free(source_code);
282284
283 source_file.close();
285 source_file.close(io);
284286 file_closed = true;
285287
286288 // Add to set after no longer possible to get error.IsDir.
......@@ -296,7 +298,7 @@ fn fmtPathFile(
296298 defer tree.deinit(gpa);
297299
298300 if (tree.errors.len != 0) {
299 try std.zig.printAstErrorsToStderr(gpa, tree, file_path, fmt.color);
301 try std.zig.printAstErrorsToStderr(gpa, io, tree, file_path, fmt.color);
300302 fmt.any_error = true;
301303 return;
302304 }
......@@ -317,7 +319,7 @@ fn fmtPathFile(
317319 try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path);
318320 var error_bundle = try wip_errors.toOwnedBundle("");
319321 defer error_bundle.deinit(gpa);
320 error_bundle.renderToStdErr(.{}, fmt.color);
322 try error_bundle.renderToStderr(io, .{}, fmt.color);
321323 fmt.any_error = true;
322324 }
323325 },
......@@ -332,7 +334,7 @@ fn fmtPathFile(
332334 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path);
333335 var error_bundle = try wip_errors.toOwnedBundle("");
334336 defer error_bundle.deinit(gpa);
335 error_bundle.renderToStdErr(.{}, fmt.color);
337 try error_bundle.renderToStderr(io, .{}, fmt.color);
336338 fmt.any_error = true;
337339 }
338340 },
......@@ -353,7 +355,7 @@ fn fmtPathFile(
353355 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});
354356 fmt.any_error = true;
355357 } else {
356 var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode, .write_buffer = &.{} });
358 var af = try dir.atomicFile(io, sub_path, .{ .permissions = stat.permissions, .write_buffer = &.{} });
357359 defer af.deinit();
358360
359361 try af.file_writer.interface.writeAll(fmt.out_buffer.written());
......@@ -368,7 +370,7 @@ pub fn main() !void {
368370 var arena_instance = std.heap.ArenaAllocator.init(gpa);
369371 const arena = arena_instance.allocator();
370372 const args = try process.argsAlloc(arena);
371 var threaded: std.Io.Threaded = .init(gpa);
373 var threaded: std.Io.Threaded = .init(gpa, .{});
372374 defer threaded.deinit();
373375 const io = threaded.io();
374376 return run(gpa, arena, io, args[1..]);
src/introspect.zig+50-48
......@@ -1,53 +1,55 @@
1const std = @import("std");
21const builtin = @import("builtin");
2const build_options = @import("build_options");
3
4const std = @import("std");
5const Io = std.Io;
6const Dir = std.Io.Dir;
37const mem = std.mem;
4const Allocator = mem.Allocator;
5const os = std.os;
6const fs = std.fs;
8const Allocator = std.mem.Allocator;
79const Cache = std.Build.Cache;
10
811const Compilation = @import("Compilation.zig");
912const Package = @import("Package.zig");
10const build_options = @import("build_options");
1113
1214/// Returns the sub_path that worked, or `null` if none did.
1315/// The path of the returned Directory is relative to `base`.
1416/// The handle of the returned Directory is open.
15fn testZigInstallPrefix(base_dir: fs.Dir) ?Cache.Directory {
16 const test_index_file = "std" ++ fs.path.sep_str ++ "std.zig";
17fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory {
18 const test_index_file = "std" ++ Dir.path.sep_str ++ "std.zig";
1719
1820 zig_dir: {
1921 // Try lib/zig/std/std.zig
20 const lib_zig = "lib" ++ fs.path.sep_str ++ "zig";
21 var test_zig_dir = base_dir.openDir(lib_zig, .{}) catch break :zig_dir;
22 const file = test_zig_dir.openFile(test_index_file, .{}) catch {
23 test_zig_dir.close();
22 const lib_zig = "lib" ++ Dir.path.sep_str ++ "zig";
23 var test_zig_dir = base_dir.openDir(io, lib_zig, .{}) catch break :zig_dir;
24 const file = test_zig_dir.openFile(io, test_index_file, .{}) catch {
25 test_zig_dir.close(io);
2426 break :zig_dir;
2527 };
26 file.close();
28 file.close(io);
2729 return .{ .handle = test_zig_dir, .path = lib_zig };
2830 }
2931
3032 // Try lib/std/std.zig
31 var test_zig_dir = base_dir.openDir("lib", .{}) catch return null;
32 const file = test_zig_dir.openFile(test_index_file, .{}) catch {
33 test_zig_dir.close();
33 var test_zig_dir = base_dir.openDir(io, "lib", .{}) catch return null;
34 const file = test_zig_dir.openFile(io, test_index_file, .{}) catch {
35 test_zig_dir.close(io);
3436 return null;
3537 };
36 file.close();
38 file.close(io);
3739 return .{ .handle = test_zig_dir, .path = "lib" };
3840}
3941
4042/// Both the directory handle and the path are newly allocated resources which the caller now owns.
41pub fn findZigLibDir(gpa: Allocator) !Cache.Directory {
43pub fn findZigLibDir(gpa: Allocator, io: Io) !Cache.Directory {
4244 const cwd_path = try getResolvedCwd(gpa);
4345 defer gpa.free(cwd_path);
44 const self_exe_path = try fs.selfExePathAlloc(gpa);
46 const self_exe_path = try std.process.executablePathAlloc(io, gpa);
4547 defer gpa.free(self_exe_path);
4648
47 return findZigLibDirFromSelfExe(gpa, cwd_path, self_exe_path);
49 return findZigLibDirFromSelfExe(gpa, io, cwd_path, self_exe_path);
4850}
4951
50/// Like `std.process.getCwdAlloc`, but also resolves the path with `std.fs.path.resolve`. This
52/// Like `std.process.getCwdAlloc`, but also resolves the path with `Dir.path.resolve`. This
5153/// means the path has no repeated separators, no "." or ".." components, and no trailing separator.
5254/// On WASI, "" is returned instead of ".".
5355pub fn getResolvedCwd(gpa: Allocator) error{
......@@ -65,27 +67,28 @@ pub fn getResolvedCwd(gpa: Allocator) error{
6567 }
6668 const cwd = try std.process.getCwdAlloc(gpa);
6769 defer gpa.free(cwd);
68 const resolved = try fs.path.resolve(gpa, &.{cwd});
69 std.debug.assert(fs.path.isAbsolute(resolved));
70 const resolved = try Dir.path.resolve(gpa, &.{cwd});
71 std.debug.assert(Dir.path.isAbsolute(resolved));
7072 return resolved;
7173}
7274
7375/// Both the directory handle and the path are newly allocated resources which the caller now owns.
7476pub fn findZigLibDirFromSelfExe(
7577 allocator: Allocator,
78 io: Io,
7679 /// The return value of `getResolvedCwd`.
7780 /// Passed as an argument to avoid pointlessly repeating the call.
7881 cwd_path: []const u8,
7982 self_exe_path: []const u8,
8083) error{ OutOfMemory, FileNotFound }!Cache.Directory {
81 const cwd = fs.cwd();
84 const cwd = Io.Dir.cwd();
8285 var cur_path: []const u8 = self_exe_path;
83 while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) {
84 var base_dir = cwd.openDir(dirname, .{}) catch continue;
85 defer base_dir.close();
86 while (Dir.path.dirname(cur_path)) |dirname| : (cur_path = dirname) {
87 var base_dir = cwd.openDir(io, dirname, .{}) catch continue;
88 defer base_dir.close(io);
8689
87 const sub_directory = testZigInstallPrefix(base_dir) orelse continue;
88 const p = try fs.path.join(allocator, &.{ dirname, sub_directory.path.? });
90 const sub_directory = testZigInstallPrefix(io, base_dir) orelse continue;
91 const p = try Dir.path.join(allocator, &.{ dirname, sub_directory.path.? });
8992 defer allocator.free(p);
9093
9194 const resolved = try resolvePath(allocator, cwd_path, &.{p});
......@@ -109,18 +112,18 @@ pub fn resolveGlobalCacheDir(allocator: Allocator) ![]u8 {
109112 if (builtin.os.tag != .windows) {
110113 if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| {
111114 if (cache_root.len > 0) {
112 return fs.path.join(allocator, &.{ cache_root, appname });
115 return Dir.path.join(allocator, &.{ cache_root, appname });
113116 }
114117 }
115118 if (std.zig.EnvVar.HOME.getPosix()) |home| {
116 return fs.path.join(allocator, &.{ home, ".cache", appname });
119 return Dir.path.join(allocator, &.{ home, ".cache", appname });
117120 }
118121 }
119122
120 return fs.getAppDataDir(allocator, appname);
123 return std.fs.getAppDataDir(allocator, appname);
121124}
122125
123/// Similar to `fs.path.resolve`, but converts to a cwd-relative path, or, if that would
126/// Similar to `Dir.path.resolve`, but converts to a cwd-relative path, or, if that would
124127/// start with a relative up-dir (".."), an absolute path based on the cwd. Also, the cwd
125128/// returns the empty string ("") instead of ".".
126129pub fn resolvePath(
......@@ -132,7 +135,7 @@ pub fn resolvePath(
132135) Allocator.Error![]u8 {
133136 if (builtin.target.os.tag == .wasi) {
134137 std.debug.assert(mem.eql(u8, cwd_resolved, ""));
135 const res = try fs.path.resolve(gpa, paths);
138 const res = try Dir.path.resolve(gpa, paths);
136139 if (mem.eql(u8, res, ".")) {
137140 gpa.free(res);
138141 return "";
......@@ -142,16 +145,16 @@ pub fn resolvePath(
142145
143146 // Heuristic for a fast path: if no component is absolute and ".." never appears, we just need to resolve `paths`.
144147 for (paths) |p| {
145 if (fs.path.isAbsolute(p)) break; // absolute path
148 if (Dir.path.isAbsolute(p)) break; // absolute path
146149 if (mem.indexOf(u8, p, "..") != null) break; // may contain up-dir
147150 } else {
148151 // no absolute path, no "..".
149 const res = try fs.path.resolve(gpa, paths);
152 const res = try Dir.path.resolve(gpa, paths);
150153 if (mem.eql(u8, res, ".")) {
151154 gpa.free(res);
152155 return "";
153156 }
154 std.debug.assert(!fs.path.isAbsolute(res));
157 std.debug.assert(!Dir.path.isAbsolute(res));
155158 std.debug.assert(!isUpDir(res));
156159 return res;
157160 }
......@@ -160,19 +163,19 @@ pub fn resolvePath(
160163 // Optimization: `paths` often has just one element.
161164 const path_resolved = switch (paths.len) {
162165 0 => unreachable,
163 1 => try fs.path.resolve(gpa, &.{ cwd_resolved, paths[0] }),
166 1 => try Dir.path.resolve(gpa, &.{ cwd_resolved, paths[0] }),
164167 else => r: {
165168 const all_paths = try gpa.alloc([]const u8, paths.len + 1);
166169 defer gpa.free(all_paths);
167170 all_paths[0] = cwd_resolved;
168171 @memcpy(all_paths[1..], paths);
169 break :r try fs.path.resolve(gpa, all_paths);
172 break :r try Dir.path.resolve(gpa, all_paths);
170173 },
171174 };
172175 errdefer gpa.free(path_resolved);
173176
174 std.debug.assert(fs.path.isAbsolute(path_resolved));
175 std.debug.assert(fs.path.isAbsolute(cwd_resolved));
177 std.debug.assert(Dir.path.isAbsolute(path_resolved));
178 std.debug.assert(Dir.path.isAbsolute(cwd_resolved));
176179
177180 if (!std.mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd
178181 if (path_resolved.len == cwd_resolved.len) {
......@@ -180,7 +183,7 @@ pub fn resolvePath(
180183 gpa.free(path_resolved);
181184 return "";
182185 }
183 if (path_resolved[cwd_resolved.len] != std.fs.path.sep) return path_resolved; // not in cwd (last component differs)
186 if (path_resolved[cwd_resolved.len] != Dir.path.sep) return path_resolved; // not in cwd (last component differs)
184187
185188 // in cwd; extract sub path
186189 const sub_path = try gpa.dupe(u8, path_resolved[cwd_resolved.len + 1 ..]);
......@@ -188,9 +191,8 @@ pub fn resolvePath(
188191 return sub_path;
189192}
190193
191/// TODO move this to std.fs.path
192194pub fn isUpDir(p: []const u8) bool {
193 return mem.startsWith(u8, p, "..") and (p.len == 2 or p[2] == fs.path.sep);
195 return mem.startsWith(u8, p, "..") and (p.len == 2 or p[2] == Dir.path.sep);
194196}
195197
196198pub const default_local_zig_cache_basename = ".zig-cache";
......@@ -198,15 +200,15 @@ pub const default_local_zig_cache_basename = ".zig-cache";
198200/// Searches upwards from `cwd` for a directory containing a `build.zig` file.
199201/// If such a directory is found, returns the path to it joined to the `.zig_cache` name.
200202/// Otherwise, returns `null`, indicating no suitable local cache location.
201pub fn resolveSuitableLocalCacheDir(arena: Allocator, cwd: []const u8) Allocator.Error!?[]u8 {
203pub fn resolveSuitableLocalCacheDir(arena: Allocator, io: Io, cwd: []const u8) Allocator.Error!?[]u8 {
202204 var cur_dir = cwd;
203205 while (true) {
204 const joined = try fs.path.join(arena, &.{ cur_dir, Package.build_zig_basename });
205 if (fs.cwd().access(joined, .{})) |_| {
206 return try fs.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename });
206 const joined = try Dir.path.join(arena, &.{ cur_dir, Package.build_zig_basename });
207 if (Io.Dir.cwd().access(io, joined, .{})) |_| {
208 return try Dir.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename });
207209 } else |err| switch (err) {
208210 error.FileNotFound => {
209 cur_dir = fs.path.dirname(cur_dir) orelse return null;
211 cur_dir = Dir.path.dirname(cur_dir) orelse return null;
210212 continue;
211213 },
212214 else => return null,
src/libs/freebsd.zig+14-14
......@@ -1,9 +1,9 @@
11const std = @import("std");
2const Io = std.Io;
23const Allocator = std.mem.Allocator;
34const mem = std.mem;
45const log = std.log;
5const fs = std.fs;
6const path = fs.path;
6const path = std.Io.Dir.path;
77const assert = std.debug.assert;
88const Version = std.SemanticVersion;
99const Path = std.Build.Cache.Path;
......@@ -401,8 +401,8 @@ pub const BuiltSharedObjects = struct {
401401 lock: Cache.Lock,
402402 dir_path: Path,
403403
404 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void {
405 self.lock.release();
404 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void {
405 self.lock.release(io);
406406 gpa.free(self.dir_path.sub_path);
407407 self.* = undefined;
408408 }
......@@ -444,12 +444,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
444444 var cache: Cache = .{
445445 .gpa = gpa,
446446 .io = io,
447 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
447 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),
448448 };
449 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
449 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
450450 cache.addPrefix(comp.dirs.zig_lib);
451451 cache.addPrefix(comp.dirs.global_cache);
452 defer cache.manifest_dir.close();
452 defer cache.manifest_dir.close(io);
453453
454454 var man = cache.obtain();
455455 defer man.deinit();
......@@ -468,7 +468,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
468468 .lock = man.toOwnedLock(),
469469 .dir_path = .{
470470 .root_dir = comp.dirs.global_cache,
471 .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest),
471 .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest),
472472 },
473473 });
474474 }
......@@ -477,10 +477,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
477477 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
478478
479479 var o_directory: Cache.Directory = .{
480 .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}),
480 .handle = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}),
481481 .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),
482482 };
483 defer o_directory.handle.close();
483 defer o_directory.handle.close(io);
484484
485485 const abilists_contents = man.files.keys()[abilists_index].contents.?;
486486 const metadata = try loadMetaData(gpa, abilists_contents);
......@@ -520,7 +520,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
520520 for (metadata.all_versions[0 .. target_ver_index + 1]) |ver| {
521521 try map_contents.print("FBSD_{d}.{d} {{ }};\n", .{ ver.major, ver.minor });
522522 }
523 try o_directory.handle.writeFile(.{ .sub_path = all_map_basename, .data = map_contents.items });
523 try o_directory.handle.writeFile(io, .{ .sub_path = all_map_basename, .data = map_contents.items });
524524 map_contents.deinit();
525525 }
526526
......@@ -974,7 +974,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
974974
975975 var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "stdthreads", etc.
976976 const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;
977 try o_directory.handle.writeFile(.{ .sub_path = asm_file_basename, .data = stubs_asm.items });
977 try o_directory.handle.writeFile(io, .{ .sub_path = asm_file_basename, .data = stubs_asm.items });
978978 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
979979 }
980980
......@@ -986,7 +986,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
986986 .lock = man.toOwnedLock(),
987987 .dir_path = .{
988988 .root_dir = comp.dirs.global_cache,
989 .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest),
989 .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest),
990990 },
991991 });
992992}
......@@ -1014,7 +1014,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C
10141014 const so_path: Path = .{
10151015 .root_dir = so_files.dir_path.root_dir,
10161016 .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{
1017 so_files.dir_path.sub_path, fs.path.sep, lib.name, lib.getSoVersion(&target.os),
1017 so_files.dir_path.sub_path, path.sep, lib.name, lib.getSoVersion(&target.os),
10181018 }) catch return comp.setAllocFailure(),
10191019 };
10201020 task_buffer[task_buffer_i] = .{ .load_dso = so_path };
src/libs/glibc.zig+16-16
......@@ -1,9 +1,9 @@
11const std = @import("std");
2const Io = std.Io;
23const Allocator = std.mem.Allocator;
34const mem = std.mem;
45const log = std.log;
5const fs = std.fs;
6const path = fs.path;
6const path = std.Io.Dir.path;
77const assert = std.debug.assert;
88const Version = std.SemanticVersion;
99const Path = std.Build.Cache.Path;
......@@ -640,8 +640,8 @@ pub const BuiltSharedObjects = struct {
640640 lock: Cache.Lock,
641641 dir_path: Path,
642642
643 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void {
644 self.lock.release();
643 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void {
644 self.lock.release(io);
645645 gpa.free(self.dir_path.sub_path);
646646 self.* = undefined;
647647 }
......@@ -679,12 +679,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
679679 var cache: Cache = .{
680680 .gpa = gpa,
681681 .io = io,
682 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
682 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),
683683 };
684 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
684 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
685685 cache.addPrefix(comp.dirs.zig_lib);
686686 cache.addPrefix(comp.dirs.global_cache);
687 defer cache.manifest_dir.close();
687 defer cache.manifest_dir.close(io);
688688
689689 var man = cache.obtain();
690690 defer man.deinit();
......@@ -703,7 +703,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
703703 .lock = man.toOwnedLock(),
704704 .dir_path = .{
705705 .root_dir = comp.dirs.global_cache,
706 .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest),
706 .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest),
707707 },
708708 });
709709 }
......@@ -712,10 +712,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
712712 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
713713
714714 var o_directory: Cache.Directory = .{
715 .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}),
715 .handle = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}),
716716 .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),
717717 };
718 defer o_directory.handle.close();
718 defer o_directory.handle.close(io);
719719
720720 const abilists_contents = man.files.keys()[abilists_index].contents.?;
721721 const metadata = try loadMetaData(gpa, abilists_contents);
......@@ -759,7 +759,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
759759 try map_contents.print("GLIBC_{d}.{d}.{d} {{ }};\n", .{ ver.major, ver.minor, ver.patch });
760760 }
761761 }
762 try o_directory.handle.writeFile(.{ .sub_path = all_map_basename, .data = map_contents.items });
762 try o_directory.handle.writeFile(io, .{ .sub_path = all_map_basename, .data = map_contents.items });
763763 map_contents.deinit(); // The most recent allocation of an arena can be freed :)
764764 }
765765
......@@ -775,7 +775,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
775775 try stubs_asm.appendSlice(".text\n");
776776
777777 var sym_i: usize = 0;
778 var sym_name_buf: std.Io.Writer.Allocating = .init(arena);
778 var sym_name_buf: Io.Writer.Allocating = .init(arena);
779779 var opt_symbol_name: ?[]const u8 = null;
780780 var versions_buffer: [32]u8 = undefined;
781781 var versions_len: usize = undefined;
......@@ -796,7 +796,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
796796 // twice, which causes a "duplicate symbol" assembler error.
797797 var versions_written = std.AutoArrayHashMap(Version, void).init(arena);
798798
799 var inc_reader: std.Io.Reader = .fixed(metadata.inclusions);
799 var inc_reader: Io.Reader = .fixed(metadata.inclusions);
800800
801801 const fn_inclusions_len = try inc_reader.takeInt(u16, .little);
802802
......@@ -1118,7 +1118,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
11181118
11191119 var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc.
11201120 const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;
1121 try o_directory.handle.writeFile(.{ .sub_path = asm_file_basename, .data = stubs_asm.items });
1121 try o_directory.handle.writeFile(io, .{ .sub_path = asm_file_basename, .data = stubs_asm.items });
11221122 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
11231123 }
11241124
......@@ -1130,7 +1130,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
11301130 .lock = man.toOwnedLock(),
11311131 .dir_path = .{
11321132 .root_dir = comp.dirs.global_cache,
1133 .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest),
1133 .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest),
11341134 },
11351135 });
11361136}
......@@ -1156,7 +1156,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C
11561156 const so_path: Path = .{
11571157 .root_dir = so_files.dir_path.root_dir,
11581158 .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{
1159 so_files.dir_path.sub_path, fs.path.sep, lib.name, lib.sover,
1159 so_files.dir_path.sub_path, path.sep, lib.name, lib.sover,
11601160 }) catch return comp.setAllocFailure(),
11611161 };
11621162 task_buffer[task_buffer_i] = .{ .load_dso = so_path };
src/libs/mingw.zig+39-26
......@@ -1,7 +1,8 @@
11const std = @import("std");
2const Io = std.Io;
23const Allocator = std.mem.Allocator;
34const mem = std.mem;
4const path = std.fs.path;
5const path = std.Io.Dir.path;
56const assert = std.debug.assert;
67const log = std.log.scoped(.mingw);
78
......@@ -241,7 +242,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
241242 defer arena_allocator.deinit();
242243 const arena = arena_allocator.allocator();
243244
244 const def_file_path = findDef(arena, comp.getTarget(), comp.dirs.zig_lib, lib_name) catch |err| switch (err) {
245 const def_file_path = findDef(arena, io, comp.getTarget(), comp.dirs.zig_lib, lib_name) catch |err| switch (err) {
245246 error.FileNotFound => {
246247 log.debug("no {s}.def file available to make a DLL import {s}.lib", .{ lib_name, lib_name });
247248 // In this case we will end up putting foo.lib onto the linker line and letting the linker
......@@ -257,12 +258,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
257258 var cache: Cache = .{
258259 .gpa = gpa,
259260 .io = io,
260 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
261 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),
261262 };
262 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
263 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
263264 cache.addPrefix(comp.dirs.zig_lib);
264265 cache.addPrefix(comp.dirs.global_cache);
265 defer cache.manifest_dir.close();
266 defer cache.manifest_dir.close(io);
266267
267268 cache.hash.addBytes(build_options.version);
268269 cache.hash.addOptionalBytes(comp.dirs.zig_lib.path);
......@@ -296,26 +297,32 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
296297
297298 const digest = man.final();
298299 const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest });
299 var o_dir = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{});
300 defer o_dir.close();
300 var o_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{});
301 defer o_dir.close(io);
301302
302303 const aro = @import("aro");
303304 var diagnostics: aro.Diagnostics = .{
304305 .output = .{ .to_list = .{ .arena = .init(gpa) } },
305306 };
306307 defer diagnostics.deinit();
307 var aro_comp = aro.Compilation.init(gpa, arena, io, &diagnostics, std.fs.cwd());
308 var aro_comp = aro.Compilation.init(gpa, arena, io, &diagnostics, Io.Dir.cwd());
308309 defer aro_comp.deinit();
309310
310311 aro_comp.target = .fromZigTarget(target.*);
311312
312313 const include_dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "mingw", "def-include" });
313314
314 if (comp.verbose_cc) print: {
315 var stderr, _ = std.debug.lockStderrWriter(&.{});
316 defer std.debug.unlockStderrWriter();
317 nosuspend stderr.print("def file: {s}\n", .{def_file_path}) catch break :print;
318 nosuspend stderr.print("include dir: {s}\n", .{include_dir}) catch break :print;
315 if (comp.verbose_cc) {
316 var buffer: [256]u8 = undefined;
317 const stderr = try io.lockStderr(&buffer, null);
318 defer io.unlockStderr();
319 const w = &stderr.file_writer.interface;
320 w.print("def file: {s}\n", .{def_file_path}) catch |err| switch (err) {
321 error.WriteFailed => return stderr.file_writer.err.?,
322 };
323 w.print("include dir: {s}\n", .{include_dir}) catch |err| switch (err) {
324 error.WriteFailed => return stderr.file_writer.err.?,
325 };
319326 }
320327
321328 try aro_comp.search_path.append(gpa, .{ .path = include_dir, .kind = .normal });
......@@ -332,18 +339,21 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
332339
333340 if (aro_comp.diagnostics.output.to_list.messages.items.len != 0) {
334341 var buffer: [64]u8 = undefined;
335 const w, const ttyconf = std.debug.lockStderrWriter(&buffer);
336 defer std.debug.unlockStderrWriter();
342 const stderr = try io.lockStderr(&buffer, null);
343 defer io.unlockStderr();
337344 for (aro_comp.diagnostics.output.to_list.messages.items) |msg| {
338345 if (msg.kind == .@"fatal error" or msg.kind == .@"error") {
339 msg.write(w, ttyconf, true) catch {};
346 msg.write(stderr.terminal(), true) catch |err| switch (err) {
347 error.WriteFailed => return stderr.file_writer.err.?,
348 error.Unexpected => |e| return e,
349 };
340350 return error.AroPreprocessorFailed;
341351 }
342352 }
343353 }
344354
345355 const members = members: {
346 var aw: std.Io.Writer.Allocating = .init(gpa);
356 var aw: Io.Writer.Allocating = .init(gpa);
347357 errdefer aw.deinit();
348358 try pp.prettyPrintTokens(&aw.writer, .result_only);
349359
......@@ -356,8 +366,9 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
356366 error.OutOfMemory => |e| return e,
357367 error.ParseError => {
358368 var buffer: [64]u8 = undefined;
359 const w, _ = std.debug.lockStderrWriter(&buffer);
360 defer std.debug.unlockStderrWriter();
369 const stderr = try io.lockStderr(&buffer, null);
370 defer io.unlockStderr();
371 const w = &stderr.file_writer.interface;
361372 try w.writeAll("error: ");
362373 try def_diagnostics.writeMsg(w, input);
363374 try w.writeByte('\n');
......@@ -376,10 +387,10 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
376387 errdefer gpa.free(lib_final_path);
377388
378389 {
379 const lib_final_file = try o_dir.createFile(final_lib_basename, .{ .truncate = true });
380 defer lib_final_file.close();
390 const lib_final_file = try o_dir.createFile(io, final_lib_basename, .{ .truncate = true });
391 defer lib_final_file.close(io);
381392 var buffer: [1024]u8 = undefined;
382 var file_writer = lib_final_file.writer(&buffer);
393 var file_writer = lib_final_file.writer(io, &buffer);
383394 try implib.writeCoffArchive(gpa, &file_writer.interface, members);
384395 try file_writer.interface.flush();
385396 }
......@@ -401,11 +412,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
401412
402413pub fn libExists(
403414 allocator: Allocator,
415 io: Io,
404416 target: *const std.Target,
405417 zig_lib_directory: Cache.Directory,
406418 lib_name: []const u8,
407419) !bool {
408 const s = findDef(allocator, target, zig_lib_directory, lib_name) catch |err| switch (err) {
420 const s = findDef(allocator, io, target, zig_lib_directory, lib_name) catch |err| switch (err) {
409421 error.FileNotFound => return false,
410422 else => |e| return e,
411423 };
......@@ -417,6 +429,7 @@ pub fn libExists(
417429/// see if a .def file exists.
418430fn findDef(
419431 allocator: Allocator,
432 io: Io,
420433 target: *const std.Target,
421434 zig_lib_directory: Cache.Directory,
422435 lib_name: []const u8,
......@@ -442,7 +455,7 @@ fn findDef(
442455 } else {
443456 try override_path.print(fmt_path, .{ lib_path, lib_name });
444457 }
445 if (std.fs.cwd().access(override_path.items, .{})) |_| {
458 if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| {
446459 return override_path.toOwnedSlice();
447460 } else |err| switch (err) {
448461 error.FileNotFound => {},
......@@ -459,7 +472,7 @@ fn findDef(
459472 } else {
460473 try override_path.print(fmt_path, .{lib_name});
461474 }
462 if (std.fs.cwd().access(override_path.items, .{})) |_| {
475 if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| {
463476 return override_path.toOwnedSlice();
464477 } else |err| switch (err) {
465478 error.FileNotFound => {},
......@@ -476,7 +489,7 @@ fn findDef(
476489 } else {
477490 try override_path.print(fmt_path, .{lib_name});
478491 }
479 if (std.fs.cwd().access(override_path.items, .{})) |_| {
492 if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| {
480493 return override_path.toOwnedSlice();
481494 } else |err| switch (err) {
482495 error.FileNotFound => {},
src/libs/mingw/def.zig+22-10
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23
34pub const ModuleDefinitionType = enum {
45 mingw,
......@@ -663,7 +664,9 @@ test parse {
663664 \\
664665 ;
665666
666 try testParse(.AMD64, source, "foo.dll", &[_]ModuleDefinition.Export{
667 const io = std.testing.io;
668
669 try testParse(io, .AMD64, source, "foo.dll", &[_]ModuleDefinition.Export{
667670 .{
668671 .name = "foo",
669672 .mangled_symbol_name = null,
......@@ -743,7 +746,7 @@ test parse {
743746 },
744747 });
745748
746 try testParse(.I386, source, "foo.dll", &[_]ModuleDefinition.Export{
749 try testParse(io, .I386, source, "foo.dll", &[_]ModuleDefinition.Export{
747750 .{
748751 .name = "_foo",
749752 .mangled_symbol_name = null,
......@@ -823,7 +826,7 @@ test parse {
823826 },
824827 });
825828
826 try testParse(.ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{
829 try testParse(io, .ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{
827830 .{
828831 .name = "foo",
829832 .mangled_symbol_name = null,
......@@ -903,7 +906,7 @@ test parse {
903906 },
904907 });
905908
906 try testParse(.ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{
909 try testParse(io, .ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{
907910 .{
908911 .name = "foo",
909912 .mangled_symbol_name = null,
......@@ -997,7 +1000,9 @@ test "ntdll" {
9971000 \\RtlActivateActivationContextUnsafeFast@0
9981001 ;
9991002
1000 try testParse(.AMD64, source, "ntdll.dll", &[_]ModuleDefinition.Export{
1003 const io = std.testing.io;
1004
1005 try testParse(io, .AMD64, source, "ntdll.dll", &[_]ModuleDefinition.Export{
10011006 .{
10021007 .name = "RtlDispatchAPC@12",
10031008 .mangled_symbol_name = null,
......@@ -1023,15 +1028,22 @@ test "ntdll" {
10231028 });
10241029}
10251030
1026fn testParse(machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8, expected_module_name: []const u8, expected_exports: []const ModuleDefinition.Export) !void {
1031fn testParse(
1032 io: Io,
1033 machine_type: std.coff.IMAGE.FILE.MACHINE,
1034 source: [:0]const u8,
1035 expected_module_name: []const u8,
1036 expected_exports: []const ModuleDefinition.Export,
1037) !void {
10271038 var diagnostics: Diagnostics = undefined;
10281039 const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {
10291040 error.OutOfMemory => |e| return e,
10301041 error.ParseError => {
1031 const stderr, _ = std.debug.lockStderrWriter(&.{});
1032 defer std.debug.unlockStderrWriter();
1033 try diagnostics.writeMsg(stderr, source);
1034 try stderr.writeByte('\n');
1042 const stderr = try io.lockStderr(&.{}, null);
1043 defer io.unlockStderr();
1044 const w = &stderr.file_writer.interface;
1045 try diagnostics.writeMsg(w, source);
1046 try w.writeByte('\n');
10351047 return err;
10361048 },
10371049 };
src/libs/netbsd.zig+13-13
......@@ -1,9 +1,9 @@
11const std = @import("std");
2const Io = std.Io;
23const Allocator = std.mem.Allocator;
34const mem = std.mem;
45const log = std.log;
5const fs = std.fs;
6const path = fs.path;
6const path = std.Io.Dir.path;
77const assert = std.debug.assert;
88const Version = std.SemanticVersion;
99const Path = std.Build.Cache.Path;
......@@ -346,8 +346,8 @@ pub const BuiltSharedObjects = struct {
346346 lock: Cache.Lock,
347347 dir_path: Path,
348348
349 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void {
350 self.lock.release();
349 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void {
350 self.lock.release(io);
351351 gpa.free(self.dir_path.sub_path);
352352 self.* = undefined;
353353 }
......@@ -385,12 +385,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
385385 var cache: Cache = .{
386386 .gpa = gpa,
387387 .io = io,
388 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
388 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),
389389 };
390 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
390 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
391391 cache.addPrefix(comp.dirs.zig_lib);
392392 cache.addPrefix(comp.dirs.global_cache);
393 defer cache.manifest_dir.close();
393 defer cache.manifest_dir.close(io);
394394
395395 var man = cache.obtain();
396396 defer man.deinit();
......@@ -409,7 +409,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
409409 .lock = man.toOwnedLock(),
410410 .dir_path = .{
411411 .root_dir = comp.dirs.global_cache,
412 .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest),
412 .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest),
413413 },
414414 });
415415 }
......@@ -418,10 +418,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
418418 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
419419
420420 var o_directory: Cache.Directory = .{
421 .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}),
421 .handle = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{}),
422422 .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),
423423 };
424 defer o_directory.handle.close();
424 defer o_directory.handle.close(io);
425425
426426 const abilists_contents = man.files.keys()[abilists_index].contents.?;
427427 const metadata = try loadMetaData(gpa, abilists_contents);
......@@ -628,7 +628,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
628628
629629 var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc.
630630 const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;
631 try o_directory.handle.writeFile(.{ .sub_path = asm_file_basename, .data = stubs_asm.items });
631 try o_directory.handle.writeFile(io, .{ .sub_path = asm_file_basename, .data = stubs_asm.items });
632632 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
633633 }
634634
......@@ -640,7 +640,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
640640 .lock = man.toOwnedLock(),
641641 .dir_path = .{
642642 .root_dir = comp.dirs.global_cache,
643 .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest),
643 .sub_path = try gpa.dupe(u8, "o" ++ path.sep_str ++ digest),
644644 },
645645 });
646646}
......@@ -661,7 +661,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C
661661 const so_path: Path = .{
662662 .root_dir = so_files.dir_path.root_dir,
663663 .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{
664 so_files.dir_path.sub_path, fs.path.sep, lib.name, lib.sover,
664 so_files.dir_path.sub_path, path.sep, lib.name, lib.sover,
665665 }) catch return comp.setAllocFailure(),
666666 };
667667 task_buffer[task_buffer_i] = .{ .load_dso = so_path };
src/link.zig+138-86
......@@ -393,7 +393,7 @@ pub const File = struct {
393393 comp: *Compilation,
394394 emit: Path,
395395
396 file: ?fs.File,
396 file: ?Io.File,
397397 /// When using the LLVM backend, the emitted object is written to a file with this name. This
398398 /// object file then becomes a normal link input to LLD or a self-hosted linker.
399399 ///
......@@ -620,16 +620,16 @@ pub const File = struct {
620620 emit.sub_path, std.crypto.random.int(u32),
621621 });
622622 defer gpa.free(tmp_sub_path);
623 try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, .{});
624 try emit.root_dir.handle.rename(tmp_sub_path, emit.sub_path);
623 try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, io, .{});
624 try emit.root_dir.handle.rename(tmp_sub_path, emit.root_dir.handle, emit.sub_path, io);
625625 switch (builtin.os.tag) {
626626 .linux => std.posix.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| {
627 log.warn("ptrace failure: {s}", .{@errorName(err)});
627 log.warn("ptrace failure: {t}", .{err});
628628 },
629629 .maccatalyst, .macos => {
630630 const macho_file = base.cast(.macho).?;
631631 macho_file.ptraceAttach(pid) catch |err| {
632 log.warn("attaching failed with error: {s}", .{@errorName(err)});
632 log.warn("attaching failed with error: {t}", .{err});
633633 };
634634 },
635635 .windows => unreachable,
......@@ -637,7 +637,7 @@ pub const File = struct {
637637 }
638638 }
639639 }
640 base.file = try emit.root_dir.handle.openFile(emit.sub_path, .{ .mode = .read_write });
640 base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write });
641641 },
642642 .elf2, .coff2 => if (base.file == null) {
643643 const mf = if (base.cast(.elf2)) |elf|
......@@ -646,10 +646,10 @@ pub const File = struct {
646646 &coff.mf
647647 else
648648 unreachable;
649 mf.file = try base.emit.root_dir.handle.adaptToNewApi().openFile(io, base.emit.sub_path, .{
649 mf.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{
650650 .mode = .read_write,
651651 });
652 base.file = .adaptFromNewApi(mf.file);
652 base.file = mf.file;
653653 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));
654654 },
655655 .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }),
......@@ -687,7 +687,7 @@ pub const File = struct {
687687 .lld => assert(base.file == null),
688688 .elf => if (base.file) |f| {
689689 dev.check(.elf_linker);
690 f.close();
690 f.close(io);
691691 base.file = null;
692692
693693 if (base.child_pid) |pid| {
......@@ -701,7 +701,7 @@ pub const File = struct {
701701 },
702702 .macho, .wasm => if (base.file) |f| {
703703 dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker });
704 f.close();
704 f.close(io);
705705 base.file = null;
706706
707707 if (base.child_pid) |pid| {
......@@ -852,10 +852,12 @@ pub const File = struct {
852852 }
853853 }
854854
855 pub fn releaseLock(self: *File) void {
856 if (self.lock) |*lock| {
857 lock.release();
858 self.lock = null;
855 pub fn releaseLock(base: *File) void {
856 const comp = base.comp;
857 const io = comp.io;
858 if (base.lock) |*lock| {
859 lock.release(io);
860 base.lock = null;
859861 }
860862 }
861863
......@@ -866,8 +868,9 @@ pub const File = struct {
866868 }
867869
868870 pub fn destroy(base: *File) void {
871 const io = base.comp.io;
869872 base.releaseLock();
870 if (base.file) |f| f.close();
873 if (base.file) |f| f.close(io);
871874 switch (base.tag) {
872875 .plan9 => unreachable,
873876 inline else => |tag| {
......@@ -897,16 +900,16 @@ pub const File = struct {
897900 }
898901 }
899902
900 pub const FlushError = error{
903 pub const FlushError = Io.Cancelable || Allocator.Error || error{
901904 /// Indicates an error will be present in `Compilation.link_diags`.
902905 LinkFailure,
903 OutOfMemory,
904906 };
905907
906908 /// Commit pending changes and write headers. Takes into account final output mode.
907909 /// `arena` has the lifetime of the call to `Compilation.update`.
908910 pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void {
909911 const comp = base.comp;
912 const io = comp.io;
910913 if (comp.clang_preprocessor_mode == .yes or comp.clang_preprocessor_mode == .pch) {
911914 dev.check(.clang_command);
912915 const emit = base.emit;
......@@ -917,12 +920,19 @@ pub const File = struct {
917920 assert(comp.c_object_table.count() == 1);
918921 const the_key = comp.c_object_table.keys()[0];
919922 const cached_pp_file_path = the_key.status.success.object_path;
920 cached_pp_file_path.root_dir.handle.copyFile(cached_pp_file_path.sub_path, emit.root_dir.handle, emit.sub_path, .{}) catch |err| {
923 Io.Dir.copyFile(
924 cached_pp_file_path.root_dir.handle,
925 cached_pp_file_path.sub_path,
926 emit.root_dir.handle,
927 emit.sub_path,
928 io,
929 .{},
930 ) catch |err| {
921931 const diags = &base.comp.link_diags;
922 return diags.fail("failed to copy '{f}' to '{f}': {s}", .{
932 return diags.fail("failed to copy '{f}' to '{f}': {t}", .{
923933 std.fmt.alt(@as(Path, cached_pp_file_path), .formatEscapeChar),
924934 std.fmt.alt(@as(Path, emit), .formatEscapeChar),
925 @errorName(err),
935 err,
926936 });
927937 };
928938 return;
......@@ -1060,9 +1070,10 @@ pub const File = struct {
10601070 /// Opens a path as an object file and parses it into the linker.
10611071 fn openLoadObject(base: *File, path: Path) anyerror!void {
10621072 if (base.tag == .lld) return;
1073 const io = base.comp.io;
10631074 const diags = &base.comp.link_diags;
1064 const input = try openObjectInput(diags, path);
1065 errdefer input.object.file.close();
1075 const input = try openObjectInput(io, diags, path);
1076 errdefer input.object.file.close(io);
10661077 try loadInput(base, input);
10671078 }
10681079
......@@ -1070,21 +1081,22 @@ pub const File = struct {
10701081 /// If `query` is non-null, allows GNU ld scripts.
10711082 fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void {
10721083 if (base.tag == .lld) return;
1084 const io = base.comp.io;
10731085 if (opt_query) |query| {
1074 const archive = try openObject(path, query.must_link, query.hidden);
1075 errdefer archive.file.close();
1086 const archive = try openObject(io, path, query.must_link, query.hidden);
1087 errdefer archive.file.close(io);
10761088 loadInput(base, .{ .archive = archive }) catch |err| switch (err) {
10771089 error.BadMagic, error.UnexpectedEndOfFile => {
10781090 if (base.tag != .elf and base.tag != .elf2) return err;
10791091 try loadGnuLdScript(base, path, query, archive.file);
1080 archive.file.close();
1092 archive.file.close(io);
10811093 return;
10821094 },
10831095 else => return err,
10841096 };
10851097 } else {
1086 const archive = try openObject(path, false, false);
1087 errdefer archive.file.close();
1098 const archive = try openObject(io, path, false, false);
1099 errdefer archive.file.close(io);
10881100 try loadInput(base, .{ .archive = archive });
10891101 }
10901102 }
......@@ -1093,29 +1105,30 @@ pub const File = struct {
10931105 /// Handles GNU ld scripts.
10941106 fn openLoadDso(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void {
10951107 if (base.tag == .lld) return;
1096 const dso = try openDso(path, query.needed, query.weak, query.reexport);
1097 errdefer dso.file.close();
1108 const io = base.comp.io;
1109 const dso = try openDso(io, path, query.needed, query.weak, query.reexport);
1110 errdefer dso.file.close(io);
10981111 loadInput(base, .{ .dso = dso }) catch |err| switch (err) {
10991112 error.BadMagic, error.UnexpectedEndOfFile => {
11001113 if (base.tag != .elf and base.tag != .elf2) return err;
11011114 try loadGnuLdScript(base, path, query, dso.file);
1102 dso.file.close();
1115 dso.file.close(io);
11031116 return;
11041117 },
11051118 else => return err,
11061119 };
11071120 }
11081121
1109 fn loadGnuLdScript(base: *File, path: Path, parent_query: UnresolvedInput.Query, file: fs.File) anyerror!void {
1122 fn loadGnuLdScript(base: *File, path: Path, parent_query: UnresolvedInput.Query, file: Io.File) anyerror!void {
11101123 const comp = base.comp;
1124 const io = comp.io;
11111125 const diags = &comp.link_diags;
11121126 const gpa = comp.gpa;
1113 const io = comp.io;
1114 const stat = try file.stat();
1127 const stat = try file.stat(io);
11151128 const size = std.math.cast(u32, stat.size) orelse return error.FileTooBig;
11161129 const buf = try gpa.alloc(u8, size);
11171130 defer gpa.free(buf);
1118 const n = try file.preadAll(buf, 0);
1131 const n = try file.readPositionalAll(io, buf, 0);
11191132 if (buf.len != n) return error.UnexpectedEndOfFile;
11201133 var ld_script = try LdScript.parse(gpa, diags, path, buf);
11211134 defer ld_script.deinit(gpa);
......@@ -1180,6 +1193,32 @@ pub const File = struct {
11801193 }
11811194 }
11821195
1196 /// Legacy function for old linker code
1197 pub fn copyRangeAll(base: *File, old_offset: u64, new_offset: u64, size: u64) !void {
1198 const comp = base.comp;
1199 const io = comp.io;
1200 const file = base.file.?;
1201 return copyRangeAll2(io, file, file, old_offset, new_offset, size);
1202 }
1203
1204 /// Legacy function for old linker code
1205 pub fn copyRangeAll2(io: Io, src_file: Io.File, dst_file: Io.File, old_offset: u64, new_offset: u64, size: u64) !void {
1206 var write_buffer: [2048]u8 = undefined;
1207 var file_reader = src_file.reader(io, &.{});
1208 file_reader.pos = old_offset;
1209 var file_writer = dst_file.writer(io, &write_buffer);
1210 file_writer.pos = new_offset;
1211 const size_u = std.math.cast(usize, size) orelse return error.Overflow;
1212 const n = file_writer.interface.sendFileAll(&file_reader, .limited(size_u)) catch |err| switch (err) {
1213 error.ReadFailed => return file_reader.err.?,
1214 error.WriteFailed => return file_writer.err.?,
1215 };
1216 assert(n == size_u);
1217 file_writer.interface.flush() catch |err| switch (err) {
1218 error.WriteFailed => return file_writer.err.?,
1219 };
1220 }
1221
11831222 pub const Tag = enum {
11841223 coff2,
11851224 elf,
......@@ -1231,22 +1270,26 @@ pub const File = struct {
12311270 ty: InternPool.Index,
12321271 };
12331272
1234 pub fn determineMode(
1273 pub fn determinePermissions(
12351274 output_mode: std.builtin.OutputMode,
12361275 link_mode: std.builtin.LinkMode,
1237 ) fs.File.Mode {
1276 ) Io.File.Permissions {
12381277 // On common systems with a 0o022 umask, 0o777 will still result in a file created
12391278 // with 0o755 permissions, but it works appropriately if the system is configured
12401279 // more leniently. As another data point, C's fopen seems to open files with the
12411280 // 666 mode.
1242 const executable_mode = if (builtin.target.os.tag == .windows) 0 else 0o777;
1281 const executable_mode: Io.File.Permissions = if (builtin.target.os.tag == .windows)
1282 .default_file
1283 else
1284 .fromMode(0o777);
1285
12431286 switch (output_mode) {
12441287 .Lib => return switch (link_mode) {
12451288 .dynamic => executable_mode,
1246 .static => fs.File.default_mode,
1289 .static => .default_file,
12471290 },
12481291 .Exe => return executable_mode,
1249 .Obj => return fs.File.default_mode,
1292 .Obj => return .default_file,
12501293 }
12511294 }
12521295
......@@ -1656,19 +1699,19 @@ pub const Input = union(enum) {
16561699
16571700 pub const Object = struct {
16581701 path: Path,
1659 file: fs.File,
1702 file: Io.File,
16601703 must_link: bool,
16611704 hidden: bool,
16621705 };
16631706
16641707 pub const Res = struct {
16651708 path: Path,
1666 file: fs.File,
1709 file: Io.File,
16671710 };
16681711
16691712 pub const Dso = struct {
16701713 path: Path,
1671 file: fs.File,
1714 file: Io.File,
16721715 needed: bool,
16731716 weak: bool,
16741717 reexport: bool,
......@@ -1690,7 +1733,7 @@ pub const Input = union(enum) {
16901733 }
16911734
16921735 /// Returns `null` in the case of `dso_exact`.
1693 pub fn pathAndFile(input: Input) ?struct { Path, fs.File } {
1736 pub fn pathAndFile(input: Input) ?struct { Path, Io.File } {
16941737 return switch (input) {
16951738 .object, .archive => |obj| .{ obj.path, obj.file },
16961739 inline .res, .dso => |x| .{ x.path, x.file },
......@@ -1735,6 +1778,7 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {
17351778pub fn resolveInputs(
17361779 gpa: Allocator,
17371780 arena: Allocator,
1781 io: Io,
17381782 target: *const std.Target,
17391783 /// This function mutates this array but does not take ownership.
17401784 /// Allocated with `gpa`.
......@@ -1784,6 +1828,7 @@ pub fn resolveInputs(
17841828 for (lib_directories) |lib_directory| switch (try resolveLibInput(
17851829 gpa,
17861830 arena,
1831 io,
17871832 unresolved_inputs,
17881833 resolved_inputs,
17891834 &checked_paths,
......@@ -1810,6 +1855,7 @@ pub fn resolveInputs(
18101855 for (lib_directories) |lib_directory| switch (try resolveLibInput(
18111856 gpa,
18121857 arena,
1858 io,
18131859 unresolved_inputs,
18141860 resolved_inputs,
18151861 &checked_paths,
......@@ -1837,6 +1883,7 @@ pub fn resolveInputs(
18371883 switch (try resolveLibInput(
18381884 gpa,
18391885 arena,
1886 io,
18401887 unresolved_inputs,
18411888 resolved_inputs,
18421889 &checked_paths,
......@@ -1855,6 +1902,7 @@ pub fn resolveInputs(
18551902 switch (try resolveLibInput(
18561903 gpa,
18571904 arena,
1905 io,
18581906 unresolved_inputs,
18591907 resolved_inputs,
18601908 &checked_paths,
......@@ -1886,6 +1934,7 @@ pub fn resolveInputs(
18861934 if (try resolvePathInput(
18871935 gpa,
18881936 arena,
1937 io,
18891938 unresolved_inputs,
18901939 resolved_inputs,
18911940 &ld_script_bytes,
......@@ -1903,6 +1952,7 @@ pub fn resolveInputs(
19031952 switch ((try resolvePathInput(
19041953 gpa,
19051954 arena,
1955 io,
19061956 unresolved_inputs,
19071957 resolved_inputs,
19081958 &ld_script_bytes,
......@@ -1930,6 +1980,7 @@ pub fn resolveInputs(
19301980 if (try resolvePathInput(
19311981 gpa,
19321982 arena,
1983 io,
19331984 unresolved_inputs,
19341985 resolved_inputs,
19351986 &ld_script_bytes,
......@@ -1969,6 +2020,7 @@ const fatal = std.process.fatal;
19692020fn resolveLibInput(
19702021 gpa: Allocator,
19712022 arena: Allocator,
2023 io: Io,
19722024 /// Allocated via `gpa`.
19732025 unresolved_inputs: *std.ArrayList(UnresolvedInput),
19742026 /// Allocated via `gpa`.
......@@ -1994,11 +2046,11 @@ fn resolveLibInput(
19942046 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.tbd", .{lib_name}),
19952047 };
19962048 try checked_paths.print(gpa, "\n {f}", .{test_path});
1997 var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
2049 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
19982050 error.FileNotFound => break :tbd,
19992051 else => |e| fatal("unable to search for tbd library '{f}': {s}", .{ test_path, @errorName(e) }),
20002052 };
2001 errdefer file.close();
2053 errdefer file.close(io);
20022054 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query);
20032055 }
20042056
......@@ -2013,7 +2065,7 @@ fn resolveLibInput(
20132065 }),
20142066 };
20152067 try checked_paths.print(gpa, "\n {f}", .{test_path});
2016 switch (try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, .{
2068 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, .{
20172069 .path = test_path,
20182070 .query = name_query.query,
20192071 }, link_mode, color)) {
......@@ -2030,13 +2082,13 @@ fn resolveLibInput(
20302082 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}),
20312083 };
20322084 try checked_paths.print(gpa, "\n {f}", .{test_path});
2033 var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
2085 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
20342086 error.FileNotFound => break :so,
20352087 else => |e| fatal("unable to search for so library '{f}': {s}", .{
20362088 test_path, @errorName(e),
20372089 }),
20382090 };
2039 errdefer file.close();
2091 errdefer file.close(io);
20402092 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query);
20412093 }
20422094
......@@ -2048,11 +2100,11 @@ fn resolveLibInput(
20482100 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}),
20492101 };
20502102 try checked_paths.print(gpa, "\n {f}", .{test_path});
2051 var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
2103 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
20522104 error.FileNotFound => break :mingw,
20532105 else => |e| fatal("unable to search for static library '{f}': {s}", .{ test_path, @errorName(e) }),
20542106 };
2055 errdefer file.close();
2107 errdefer file.close(io);
20562108 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query);
20572109 }
20582110
......@@ -2062,7 +2114,7 @@ fn resolveLibInput(
20622114fn finishResolveLibInput(
20632115 resolved_inputs: *std.ArrayList(Input),
20642116 path: Path,
2065 file: std.fs.File,
2117 file: Io.File,
20662118 link_mode: std.builtin.LinkMode,
20672119 query: UnresolvedInput.Query,
20682120) ResolveLibInputResult {
......@@ -2087,6 +2139,7 @@ fn finishResolveLibInput(
20872139fn resolvePathInput(
20882140 gpa: Allocator,
20892141 arena: Allocator,
2142 io: Io,
20902143 /// Allocated with `gpa`.
20912144 unresolved_inputs: *std.ArrayList(UnresolvedInput),
20922145 /// Allocated with `gpa`.
......@@ -2098,12 +2151,12 @@ fn resolvePathInput(
20982151 color: std.zig.Color,
20992152) Allocator.Error!?ResolveLibInputResult {
21002153 switch (Compilation.classifyFileExt(pq.path.sub_path)) {
2101 .static_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color),
2102 .shared_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color),
2154 .static_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color),
2155 .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color),
21032156 .object => {
2104 var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|
2157 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err|
21052158 fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) });
2106 errdefer file.close();
2159 errdefer file.close(io);
21072160 try resolved_inputs.append(gpa, .{ .object = .{
21082161 .path = pq.path,
21092162 .file = file,
......@@ -2113,9 +2166,9 @@ fn resolvePathInput(
21132166 return null;
21142167 },
21152168 .res => {
2116 var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err|
2169 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err|
21172170 fatal("failed to open windows resource {f}: {s}", .{ pq.path, @errorName(err) });
2118 errdefer file.close();
2171 errdefer file.close(io);
21192172 try resolved_inputs.append(gpa, .{ .res = .{
21202173 .path = pq.path,
21212174 .file = file,
......@@ -2129,6 +2182,7 @@ fn resolvePathInput(
21292182fn resolvePathInputLib(
21302183 gpa: Allocator,
21312184 arena: Allocator,
2185 io: Io,
21322186 /// Allocated with `gpa`.
21332187 unresolved_inputs: *std.ArrayList(UnresolvedInput),
21342188 /// Allocated with `gpa`.
......@@ -2149,30 +2203,29 @@ fn resolvePathInputLib(
21492203 .static_library, .shared_library => true,
21502204 else => false,
21512205 }) {
2152 var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
2206 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
21532207 error.FileNotFound => return .no_match,
2154 else => |e| fatal("unable to search for {s} library '{f}': {s}", .{
2155 @tagName(link_mode), std.fmt.alt(test_path, .formatEscapeChar), @errorName(e),
2208 else => |e| fatal("unable to search for {t} library '{f}': {t}", .{
2209 link_mode, std.fmt.alt(test_path, .formatEscapeChar), e,
21562210 }),
21572211 };
2158 errdefer file.close();
2212 errdefer file.close(io);
21592213 try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len));
2160 const n = file.preadAll(ld_script_bytes.items, 0) catch |err| fatal("failed to read '{f}': {s}", .{
2161 std.fmt.alt(test_path, .formatEscapeChar), @errorName(err),
2162 });
2214 const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err|
2215 fatal("failed to read '{f}': {t}", .{ std.fmt.alt(test_path, .formatEscapeChar), err });
21632216 const buf = ld_script_bytes.items[0..n];
21642217 if (mem.startsWith(u8, buf, std.elf.MAGIC) or mem.startsWith(u8, buf, std.elf.ARMAG)) {
21652218 // Appears to be an ELF or archive file.
21662219 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query);
21672220 }
2168 const stat = file.stat() catch |err|
2169 fatal("failed to stat {f}: {s}", .{ test_path, @errorName(err) });
2221 const stat = file.stat(io) catch |err|
2222 fatal("failed to stat {f}: {t}", .{ test_path, err });
21702223 const size = std.math.cast(u32, stat.size) orelse
21712224 fatal("{f}: linker script too big", .{test_path});
21722225 try ld_script_bytes.resize(gpa, size);
21732226 const buf2 = ld_script_bytes.items[n..];
2174 const n2 = file.preadAll(buf2, n) catch |err|
2175 fatal("failed to read {f}: {s}", .{ test_path, @errorName(err) });
2227 const n2 = file.readPositionalAll(io, buf2, n) catch |err|
2228 fatal("failed to read {f}: {t}", .{ test_path, err });
21762229 if (n2 != buf2.len) fatal("failed to read {f}: unexpected end of file", .{test_path});
21772230
21782231 // This `Io` is only used for a mutex, and we know we aren't doing anything async/concurrent.
......@@ -2192,13 +2245,12 @@ fn resolvePathInputLib(
21922245 var error_bundle = try wip_errors.toOwnedBundle("");
21932246 defer error_bundle.deinit(gpa);
21942247
2195 error_bundle.renderToStdErr(.{}, color);
2196
2248 error_bundle.renderToStderr(io, .{}, color) catch {};
21972249 std.process.exit(1);
21982250 }
21992251
22002252 var ld_script = ld_script_result catch |err|
2201 fatal("{f}: failed to parse linker script: {s}", .{ test_path, @errorName(err) });
2253 fatal("{f}: failed to parse linker script: {t}", .{ test_path, err });
22022254 defer ld_script.deinit(gpa);
22032255
22042256 try unresolved_inputs.ensureUnusedCapacity(gpa, ld_script.args.len);
......@@ -2223,23 +2275,23 @@ fn resolvePathInputLib(
22232275 } });
22242276 }
22252277 }
2226 file.close();
2278 file.close(io);
22272279 return .ok;
22282280 }
22292281
2230 var file = test_path.root_dir.handle.openFile(test_path.sub_path, .{}) catch |err| switch (err) {
2282 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
22312283 error.FileNotFound => return .no_match,
22322284 else => |e| fatal("unable to search for {s} library {f}: {s}", .{
22332285 @tagName(link_mode), test_path, @errorName(e),
22342286 }),
22352287 };
2236 errdefer file.close();
2288 errdefer file.close(io);
22372289 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query);
22382290}
22392291
2240pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object {
2241 var file = try path.root_dir.handle.openFile(path.sub_path, .{});
2242 errdefer file.close();
2292pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object {
2293 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
2294 errdefer file.close(io);
22432295 return .{
22442296 .path = path,
22452297 .file = file,
......@@ -2248,9 +2300,9 @@ pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object {
22482300 };
22492301}
22502302
2251pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso {
2252 var file = try path.root_dir.handle.openFile(path.sub_path, .{});
2253 errdefer file.close();
2303pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso {
2304 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
2305 errdefer file.close(io);
22542306 return .{
22552307 .path = path,
22562308 .file = file,
......@@ -2260,20 +2312,20 @@ pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso
22602312 };
22612313}
22622314
2263pub fn openObjectInput(diags: *Diags, path: Path) error{LinkFailure}!Input {
2264 return .{ .object = openObject(path, false, false) catch |err| {
2315pub fn openObjectInput(io: Io, diags: *Diags, path: Path) error{LinkFailure}!Input {
2316 return .{ .object = openObject(io, path, false, false) catch |err| {
22652317 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });
22662318 } };
22672319}
22682320
2269pub fn openArchiveInput(diags: *Diags, path: Path, must_link: bool, hidden: bool) error{LinkFailure}!Input {
2270 return .{ .archive = openObject(path, must_link, hidden) catch |err| {
2321pub fn openArchiveInput(io: Io, diags: *Diags, path: Path, must_link: bool, hidden: bool) error{LinkFailure}!Input {
2322 return .{ .archive = openObject(io, path, must_link, hidden) catch |err| {
22712323 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });
22722324 } };
22732325}
22742326
2275pub fn openDsoInput(diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{LinkFailure}!Input {
2276 return .{ .dso = openDso(path, needed, weak, reexport) catch |err| {
2327pub fn openDsoInput(io: Io, diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{LinkFailure}!Input {
2328 return .{ .dso = openDso(io, path, needed, weak, reexport) catch |err| {
22772329 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });
22782330 } };
22792331}
src/link/C.zig+10-7
......@@ -124,6 +124,7 @@ pub fn createEmpty(
124124 emit: Path,
125125 options: link.File.OpenOptions,
126126) !*C {
127 const io = comp.io;
127128 const target = &comp.root_mod.resolved_target.result;
128129 assert(target.ofmt == .c);
129130 const optimize_mode = comp.root_mod.optimize_mode;
......@@ -135,11 +136,11 @@ pub fn createEmpty(
135136 assert(!use_lld);
136137 assert(!use_llvm);
137138
138 const file = try emit.root_dir.handle.createFile(emit.sub_path, .{
139 const file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{
139140 // Truncation is done on `flush`.
140141 .truncate = false,
141142 });
142 errdefer file.close();
143 errdefer file.close(io);
143144
144145 const c_file = try arena.create(C);
145146
......@@ -370,6 +371,7 @@ pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.P
370371 const comp = self.base.comp;
371372 const diags = &comp.link_diags;
372373 const gpa = comp.gpa;
374 const io = comp.io;
373375 const zcu = self.base.comp.zcu.?;
374376 const ip = &zcu.intern_pool;
375377 const pt: Zcu.PerThread = .activate(zcu, tid);
......@@ -507,8 +509,8 @@ pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.P
507509 }, self.getString(av_block.code));
508510
509511 const file = self.base.file.?;
510 file.setEndPos(f.file_size) catch |err| return diags.fail("failed to allocate file: {s}", .{@errorName(err)});
511 var fw = file.writer(&.{});
512 file.setLength(io, f.file_size) catch |err| return diags.fail("failed to allocate file: {t}", .{err});
513 var fw = file.writer(io, &.{});
512514 var w = &fw.interface;
513515 w.writeVecAll(f.all_buffers.items) catch |err| switch (err) {
514516 error.WriteFailed => return diags.fail("failed to write to '{f}': {s}", .{
......@@ -763,6 +765,7 @@ pub fn flushEmitH(zcu: *Zcu) !void {
763765 if (true) return; // emit-h is regressed
764766
765767 const emit_h = zcu.emit_h orelse return;
768 const io = zcu.comp.io;
766769
767770 // We collect a list of buffers to write, and write them all at once with pwritev 😎
768771 const num_buffers = emit_h.decl_table.count() + 1;
......@@ -790,14 +793,14 @@ pub fn flushEmitH(zcu: *Zcu) !void {
790793 }
791794
792795 const directory = emit_h.loc.directory orelse zcu.comp.local_cache_directory;
793 const file = try directory.handle.createFile(emit_h.loc.basename, .{
796 const file = try directory.handle.createFile(io, emit_h.loc.basename, .{
794797 // We set the end position explicitly below; by not truncating the file, we possibly
795798 // make it easier on the file system by doing 1 reallocation instead of two.
796799 .truncate = false,
797800 });
798 defer file.close();
801 defer file.close(io);
799802
800 try file.setEndPos(file_size);
803 try file.setLength(io, file_size);
801804 try file.pwritevAll(all_buffers.items, 0);
802805}
803806
src/link/Coff.zig+42-32
......@@ -1,3 +1,23 @@
1const Coff = @This();
2
3const builtin = @import("builtin");
4const native_endian = builtin.cpu.arch.endian();
5
6const std = @import("std");
7const Io = std.Io;
8const assert = std.debug.assert;
9const log = std.log.scoped(.link);
10
11const codegen = @import("../codegen.zig");
12const Compilation = @import("../Compilation.zig");
13const InternPool = @import("../InternPool.zig");
14const link = @import("../link.zig");
15const MappedFile = @import("MappedFile.zig");
16const target_util = @import("../target.zig");
17const Type = @import("../Type.zig");
18const Value = @import("../Value.zig");
19const Zcu = @import("../Zcu.zig");
20
121base: link.File,
222mf: MappedFile,
323nodes: std.MultiArrayList(Node),
......@@ -631,12 +651,14 @@ fn create(
631651 else => return error.UnsupportedCOFFArchitecture,
632652 };
633653
654 const io = comp.io;
655
634656 const coff = try arena.create(Coff);
635 const file = try path.root_dir.handle.adaptToNewApi().createFile(comp.io, path.sub_path, .{
657 const file = try path.root_dir.handle.createFile(io, path.sub_path, .{
636658 .read = true,
637 .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode),
659 .permissions = link.File.determinePermissions(comp.config.output_mode, comp.config.link_mode),
638660 });
639 errdefer file.close(comp.io);
661 errdefer file.close(io);
640662 coff.* = .{
641663 .base = .{
642664 .tag = .coff2,
......@@ -644,14 +666,14 @@ fn create(
644666 .comp = comp,
645667 .emit = path,
646668
647 .file = .adaptFromNewApi(file),
669 .file = file,
648670 .gc_sections = false,
649671 .print_gc_sections = false,
650672 .build_id = .none,
651673 .allow_shlib_undefined = false,
652674 .stack_size = 0,
653675 },
654 .mf = try .init(file, comp.gpa),
676 .mf = try .init(file, comp.gpa, io),
655677 .nodes = .empty,
656678 .import_table = .{
657679 .ni = .none,
......@@ -1727,22 +1749,20 @@ pub fn flush(
17271749 const comp = coff.base.comp;
17281750 if (comp.compiler_rt_dyn_lib) |crt_file| {
17291751 const gpa = comp.gpa;
1752 const io = comp.io;
17301753 const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{
17311754 std.fs.path.dirname(coff.base.emit.sub_path) orelse "",
17321755 std.fs.path.basename(crt_file.full_object_path.sub_path),
17331756 });
17341757 defer gpa.free(compiler_rt_sub_path);
1735 crt_file.full_object_path.root_dir.handle.copyFile(
1758 std.Io.Dir.copyFile(
1759 crt_file.full_object_path.root_dir.handle,
17361760 crt_file.full_object_path.sub_path,
17371761 coff.base.emit.root_dir.handle,
17381762 compiler_rt_sub_path,
1763 io,
17391764 .{},
1740 ) catch |err| switch (err) {
1741 else => |e| return comp.link_diags.fail("Copy '{s}' failed: {s}", .{
1742 compiler_rt_sub_path,
1743 @errorName(e),
1744 }),
1745 };
1765 ) catch |err| return comp.link_diags.fail("copy '{s}' failed: {t}", .{ compiler_rt_sub_path, err });
17461766 }
17471767}
17481768
......@@ -2358,10 +2378,16 @@ pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTe
23582378 _ = name;
23592379}
23602380
2361pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) void {
2362 const w, _ = std.debug.lockStderrWriter(&.{});
2363 defer std.debug.unlockStderrWriter();
2364 coff.printNode(tid, w, .root, 0) catch {};
2381pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) Io.Cancelable!void {
2382 const comp = coff.base.comp;
2383 const io = comp.io;
2384 var buffer: [512]u8 = undefined;
2385 const stderr = try io.lockStderr(&buffer, null);
2386 defer io.unlockStderr();
2387 const w = &stderr.file_writer.interface;
2388 coff.printNode(tid, w, .root, 0) catch |err| switch (err) {
2389 error.WriteFailed => return stderr.err.?,
2390 };
23652391}
23662392
23672393pub fn printNode(
......@@ -2459,19 +2485,3 @@ pub fn printNode(
24592485 }
24602486 }
24612487}
2462
2463const assert = std.debug.assert;
2464const builtin = @import("builtin");
2465const codegen = @import("../codegen.zig");
2466const Compilation = @import("../Compilation.zig");
2467const Coff = @This();
2468const InternPool = @import("../InternPool.zig");
2469const link = @import("../link.zig");
2470const log = std.log.scoped(.link);
2471const MappedFile = @import("MappedFile.zig");
2472const native_endian = builtin.cpu.arch.endian();
2473const std = @import("std");
2474const target_util = @import("../target.zig");
2475const Type = @import("../Type.zig");
2476const Value = @import("../Value.zig");
2477const Zcu = @import("../Zcu.zig");
src/link/Dwarf.zig+62-45
......@@ -1,3 +1,24 @@
1const Dwarf = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;
6const DW = std.dwarf;
7const Zir = std.zig.Zir;
8const assert = std.debug.assert;
9const log = std.log.scoped(.dwarf);
10const Writer = std.Io.Writer;
11
12const InternPool = @import("../InternPool.zig");
13const Module = @import("../Package.zig").Module;
14const Type = @import("../Type.zig");
15const Value = @import("../Value.zig");
16const Zcu = @import("../Zcu.zig");
17const codegen = @import("../codegen.zig");
18const dev = @import("../dev.zig");
19const link = @import("../link.zig");
20const target_info = @import("../target.zig");
21
122gpa: Allocator,
223bin_file: *link.File,
324format: DW.Format,
......@@ -27,18 +48,18 @@ pub const UpdateError = error{
2748 EndOfStream,
2849 Underflow,
2950 UnexpectedEndOfFile,
51 NonResizable,
3052} ||
3153 codegen.GenerateSymbolError ||
32 std.fs.File.OpenError ||
33 std.fs.File.SetEndPosError ||
34 std.fs.File.CopyRangeError ||
35 std.fs.File.PReadError ||
36 std.fs.File.PWriteError;
54 Io.File.OpenError ||
55 Io.File.LengthError ||
56 Io.File.ReadPositionalError ||
57 Io.File.WritePositionalError;
3758
3859pub const FlushError = UpdateError;
3960
4061pub const RelocError =
41 std.fs.File.PWriteError;
62 Io.File.PWriteError;
4263
4364pub const AddressSize = enum(u8) {
4465 @"32" = 4,
......@@ -135,11 +156,14 @@ const DebugInfo = struct {
135156
136157 fn declAbbrevCode(debug_info: *DebugInfo, unit: Unit.Index, entry: Entry.Index) !AbbrevCode {
137158 const dwarf: *Dwarf = @fieldParentPtr("debug_info", debug_info);
159 const comp = dwarf.bin_file.comp;
160 const io = comp.io;
138161 const unit_ptr = debug_info.section.getUnit(unit);
139162 const entry_ptr = unit_ptr.getEntry(entry);
140163 if (entry_ptr.len < AbbrevCode.decl_bytes) return .null;
141164 var abbrev_code_buf: [AbbrevCode.decl_bytes]u8 = undefined;
142 if (try dwarf.getFile().?.preadAll(
165 if (try dwarf.getFile().?.readPositionalAll(
166 io,
143167 &abbrev_code_buf,
144168 debug_info.section.off(dwarf) + unit_ptr.off + unit_ptr.header_len + entry_ptr.off,
145169 ) != abbrev_code_buf.len) return error.InputOutput;
......@@ -619,13 +643,10 @@ const Unit = struct {
619643
620644 fn move(unit: *Unit, sec: *Section, dwarf: *Dwarf, new_off: u32) UpdateError!void {
621645 if (unit.off == new_off) return;
622 const n = try dwarf.getFile().?.copyRangeAll(
623 sec.off(dwarf) + unit.off,
624 dwarf.getFile().?,
625 sec.off(dwarf) + new_off,
626 unit.len,
627 );
628 if (n != unit.len) return error.InputOutput;
646 const comp = dwarf.bin_file.comp;
647 const io = comp.io;
648 const file = dwarf.getFile().?;
649 try link.File.copyRangeAll2(io, file, file, sec.off(dwarf) + unit.off, sec.off(dwarf) + new_off, unit.len);
629650 unit.off = new_off;
630651 }
631652
......@@ -655,10 +676,14 @@ const Unit = struct {
655676
656677 fn replaceHeader(unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void {
657678 assert(contents.len == unit.header_len);
658 try dwarf.getFile().?.pwriteAll(contents, sec.off(dwarf) + unit.off);
679 const comp = dwarf.bin_file.comp;
680 const io = comp.io;
681 try dwarf.getFile().?.writePositionalAll(io, contents, sec.off(dwarf) + unit.off);
659682 }
660683
661684 fn writeTrailer(unit: *Unit, sec: *Section, dwarf: *Dwarf) UpdateError!void {
685 const comp = dwarf.bin_file.comp;
686 const io = comp.io;
662687 const start = unit.off + unit.header_len + if (unit.last.unwrap()) |last_entry| end: {
663688 const last_entry_ptr = unit.getEntry(last_entry);
664689 break :end last_entry_ptr.off + last_entry_ptr.len;
......@@ -688,7 +713,7 @@ const Unit = struct {
688713 assert(fw.end == extended_op_bytes + op_len_bytes);
689714 fw.writeByte(DW.LNE.padding) catch unreachable;
690715 assert(fw.end >= unit.trailer_len and fw.end <= len);
691 return dwarf.getFile().?.pwriteAll(fw.buffered(), sec.off(dwarf) + start);
716 return dwarf.getFile().?.writePositionalAll(io, fw.buffered(), sec.off(dwarf) + start);
692717 }
693718 var trailer_aw: Writer.Allocating = try .initCapacity(dwarf.gpa, len);
694719 defer trailer_aw.deinit();
......@@ -748,7 +773,7 @@ const Unit = struct {
748773 assert(tw.end == unit.trailer_len);
749774 tw.splatByteAll(fill_byte, len - unit.trailer_len) catch unreachable;
750775 assert(tw.end == len);
751 try dwarf.getFile().?.pwriteAll(trailer_aw.written(), sec.off(dwarf) + start);
776 try dwarf.getFile().?.writePositionalAll(io, trailer_aw.written(), sec.off(dwarf) + start);
752777 }
753778
754779 fn resolveRelocs(unit: *Unit, sec: *Section, dwarf: *Dwarf) RelocError!void {
......@@ -834,6 +859,8 @@ const Entry = struct {
834859 dwarf: *Dwarf,
835860 ) (UpdateError || Writer.Error)!void {
836861 assert(entry.len > 0);
862 const comp = dwarf.bin_file.comp;
863 const io = comp.io;
837864 const start = entry.off + entry.len;
838865 if (sec == &dwarf.debug_frame.section) {
839866 const len = if (entry.next.unwrap()) |next_entry|
......@@ -843,11 +870,11 @@ const Entry = struct {
843870 var unit_len_buf: [8]u8 = undefined;
844871 const unit_len_bytes = unit_len_buf[0..dwarf.sectionOffsetBytes()];
845872 dwarf.writeInt(unit_len_bytes, len - dwarf.unitLengthBytes());
846 try dwarf.getFile().?.pwriteAll(unit_len_bytes, sec.off(dwarf) + unit.off + unit.header_len + entry.off);
873 try dwarf.getFile().?.writePositionalAll(io, unit_len_bytes, sec.off(dwarf) + unit.off + unit.header_len + entry.off);
847874 const buf = try dwarf.gpa.alloc(u8, len - entry.len);
848875 defer dwarf.gpa.free(buf);
849876 @memset(buf, DW.CFA.nop);
850 try dwarf.getFile().?.pwriteAll(buf, sec.off(dwarf) + unit.off + unit.header_len + start);
877 try dwarf.getFile().?.writePositionalAll(io, buf, sec.off(dwarf) + unit.off + unit.header_len + start);
851878 return;
852879 }
853880 const len = unit.getEntry(entry.next.unwrap() orelse return).off - start;
......@@ -906,7 +933,7 @@ const Entry = struct {
906933 },
907934 } else assert(!sec.pad_entries_to_ideal and len == 0);
908935 assert(fw.end <= len);
909 try dwarf.getFile().?.pwriteAll(fw.buffered(), sec.off(dwarf) + unit.off + unit.header_len + start);
936 try dwarf.getFile().?.writePositionalAll(io, fw.buffered(), sec.off(dwarf) + unit.off + unit.header_len + start);
910937 }
911938
912939 fn resize(
......@@ -949,11 +976,13 @@ const Entry = struct {
949976
950977 fn replace(entry_ptr: *Entry, unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void {
951978 assert(contents.len == entry_ptr.len);
952 try dwarf.getFile().?.pwriteAll(contents, sec.off(dwarf) + unit.off + unit.header_len + entry_ptr.off);
979 const comp = dwarf.bin_file.comp;
980 const io = comp.io;
981 try dwarf.getFile().?.writePositionalAll(io, contents, sec.off(dwarf) + unit.off + unit.header_len + entry_ptr.off);
953982 if (false) {
954983 const buf = try dwarf.gpa.alloc(u8, sec.len);
955984 defer dwarf.gpa.free(buf);
956 _ = try dwarf.getFile().?.preadAll(buf, sec.off(dwarf));
985 _ = try dwarf.getFile().?.readPositionalAll(io, buf, sec.off(dwarf));
957986 log.info("Section{{ .first = {}, .last = {}, .off = 0x{x}, .len = 0x{x} }}", .{
958987 @intFromEnum(sec.first),
959988 @intFromEnum(sec.last),
......@@ -4682,6 +4711,8 @@ fn updateContainerTypeWriterError(
46824711}
46834712
46844713pub fn updateLineNumber(dwarf: *Dwarf, zcu: *Zcu, zir_index: InternPool.TrackedInst.Index) UpdateError!void {
4714 const comp = dwarf.bin_file.comp;
4715 const io = comp.io;
46854716 const ip = &zcu.intern_pool;
46864717
46874718 const inst_info = zir_index.resolveFull(ip).?;
......@@ -4701,7 +4732,7 @@ pub fn updateLineNumber(dwarf: *Dwarf, zcu: *Zcu, zir_index: InternPool.TrackedI
47014732
47024733 const unit = dwarf.debug_info.section.getUnit(dwarf.getUnitIfExists(file.mod.?) orelse return);
47034734 const entry = unit.getEntry(dwarf.decls.get(zir_index) orelse return);
4704 try dwarf.getFile().?.pwriteAll(&line_buf, dwarf.debug_info.section.off(dwarf) + unit.off + unit.header_len + entry.off + DebugInfo.declEntryLineOff(dwarf));
4735 try dwarf.getFile().?.writePositionalAll(io, &line_buf, dwarf.debug_info.section.off(dwarf) + unit.off + unit.header_len + entry.off + DebugInfo.declEntryLineOff(dwarf));
47054736}
47064737
47074738pub fn freeNav(dwarf: *Dwarf, nav_index: InternPool.Nav.Index) void {
......@@ -4738,6 +4769,8 @@ pub fn flush(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void {
47384769fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Error)!void {
47394770 const zcu = pt.zcu;
47404771 const ip = &zcu.intern_pool;
4772 const comp = dwarf.bin_file.comp;
4773 const io = comp.io;
47414774
47424775 {
47434776 const type_gop = try dwarf.types.getOrPut(dwarf.gpa, .anyerror_type);
......@@ -4957,7 +4990,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro
49574990 if (dwarf.debug_str.section.dirty) {
49584991 const contents = dwarf.debug_str.contents.items;
49594992 try dwarf.debug_str.section.resize(dwarf, contents.len);
4960 try dwarf.getFile().?.pwriteAll(contents, dwarf.debug_str.section.off(dwarf));
4993 try dwarf.getFile().?.writePositionalAll(io, contents, dwarf.debug_str.section.off(dwarf));
49614994 dwarf.debug_str.section.dirty = false;
49624995 }
49634996 if (dwarf.debug_line.section.dirty) {
......@@ -5069,7 +5102,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro
50695102 if (dwarf.debug_line_str.section.dirty) {
50705103 const contents = dwarf.debug_line_str.contents.items;
50715104 try dwarf.debug_line_str.section.resize(dwarf, contents.len);
5072 try dwarf.getFile().?.pwriteAll(contents, dwarf.debug_line_str.section.off(dwarf));
5105 try dwarf.getFile().?.writePositionalAll(io, contents, dwarf.debug_line_str.section.off(dwarf));
50735106 dwarf.debug_line_str.section.dirty = false;
50745107 }
50755108 if (dwarf.debug_loclists.section.dirty) {
......@@ -6350,7 +6383,7 @@ const AbbrevCode = enum {
63506383 });
63516384};
63526385
6353fn getFile(dwarf: *Dwarf) ?std.fs.File {
6386fn getFile(dwarf: *Dwarf) ?Io.File {
63546387 if (dwarf.bin_file.cast(.macho)) |macho_file| if (macho_file.d_sym) |*d_sym| return d_sym.file;
63556388 return dwarf.bin_file.file;
63566389}
......@@ -6391,9 +6424,11 @@ fn writeInt(dwarf: *Dwarf, buf: []u8, int: u64) void {
63916424}
63926425
63936426fn resolveReloc(dwarf: *Dwarf, source: u64, target: u64, size: u32) RelocError!void {
6427 const comp = dwarf.bin_file.comp;
6428 const io = comp.io;
63946429 var buf: [8]u8 = undefined;
63956430 dwarf.writeInt(buf[0..size], target);
6396 try dwarf.getFile().?.pwriteAll(buf[0..size], source);
6431 try dwarf.getFile().?.writePositionalAll(io, buf[0..size], source);
63976432}
63986433
63996434fn unitLengthBytes(dwarf: *Dwarf) u32 {
......@@ -6429,21 +6464,3 @@ const force_incremental = false;
64296464inline fn incremental(dwarf: Dwarf) bool {
64306465 return force_incremental or dwarf.bin_file.comp.config.incremental;
64316466}
6432
6433const Allocator = std.mem.Allocator;
6434const DW = std.dwarf;
6435const Dwarf = @This();
6436const InternPool = @import("../InternPool.zig");
6437const Module = @import("../Package.zig").Module;
6438const Type = @import("../Type.zig");
6439const Value = @import("../Value.zig");
6440const Zcu = @import("../Zcu.zig");
6441const Zir = std.zig.Zir;
6442const assert = std.debug.assert;
6443const codegen = @import("../codegen.zig");
6444const dev = @import("../dev.zig");
6445const link = @import("../link.zig");
6446const log = std.log.scoped(.dwarf);
6447const std = @import("std");
6448const target_info = @import("../target.zig");
6449const Writer = std.Io.Writer;
src/link/Elf.zig+56-57
......@@ -313,12 +313,14 @@ pub fn createEmpty(
313313 const is_obj = output_mode == .Obj;
314314 const is_obj_or_ar = is_obj or (output_mode == .Lib and link_mode == .static);
315315
316 const io = comp.io;
317
316318 // What path should this ELF linker code output to?
317319 const sub_path = emit.sub_path;
318 self.base.file = try emit.root_dir.handle.createFile(sub_path, .{
320 self.base.file = try emit.root_dir.handle.createFile(io, sub_path, .{
319321 .truncate = true,
320322 .read = true,
321 .mode = link.File.determineMode(output_mode, link_mode),
323 .permissions = link.File.determinePermissions(output_mode, link_mode),
322324 });
323325
324326 const gpa = comp.gpa;
......@@ -406,10 +408,12 @@ pub fn open(
406408}
407409
408410pub fn deinit(self: *Elf) void {
409 const gpa = self.base.comp.gpa;
411 const comp = self.base.comp;
412 const gpa = comp.gpa;
413 const io = comp.io;
410414
411415 for (self.file_handles.items) |fh| {
412 fh.close();
416 fh.close(io);
413417 }
414418 self.file_handles.deinit(gpa);
415419
......@@ -483,6 +487,8 @@ pub fn getUavVAddr(self: *Elf, uav: InternPool.Index, reloc_info: link.File.Relo
483487
484488/// Returns end pos of collision, if any.
485489fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 {
490 const comp = self.base.comp;
491 const io = comp.io;
486492 const small_ptr = self.ptr_width == .p32;
487493 const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);
488494 if (start < ehdr_size)
......@@ -522,7 +528,7 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 {
522528 }
523529 }
524530
525 if (at_end) try self.base.file.?.setEndPos(end);
531 if (at_end) try self.base.file.?.setLength(io, end);
526532 return null;
527533}
528534
......@@ -552,6 +558,8 @@ pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u64) !u64 {
552558}
553559
554560pub fn growSection(self: *Elf, shdr_index: u32, needed_size: u64, min_alignment: u64) !void {
561 const comp = self.base.comp;
562 const io = comp.io;
555563 const shdr = &self.sections.items(.shdr)[shdr_index];
556564
557565 if (shdr.sh_type != elf.SHT_NOBITS) {
......@@ -574,18 +582,11 @@ pub fn growSection(self: *Elf, shdr_index: u32, needed_size: u64, min_alignment:
574582 new_offset,
575583 });
576584
577 const amt = try self.base.file.?.copyRangeAll(
578 shdr.sh_offset,
579 self.base.file.?,
580 new_offset,
581 existing_size,
582 );
583 // TODO figure out what to about this error condition - how to communicate it up.
584 if (amt != existing_size) return error.InputOutput;
585 try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size);
585586
586587 shdr.sh_offset = new_offset;
587588 } else if (shdr.sh_offset + allocated_size == std.math.maxInt(u64)) {
588 try self.base.file.?.setEndPos(shdr.sh_offset + needed_size);
589 try self.base.file.?.setLength(io, shdr.sh_offset + needed_size);
589590 }
590591 }
591592
......@@ -737,8 +738,8 @@ pub fn loadInput(self: *Elf, input: link.Input) !void {
737738 .res => unreachable,
738739 .dso_exact => @panic("TODO"),
739740 .object => |obj| try parseObject(self, obj),
740 .archive => |obj| try parseArchive(gpa, diags, &self.file_handles, &self.files, target, debug_fmt_strip, default_sym_version, &self.objects, obj, is_static_lib),
741 .dso => |dso| try parseDso(gpa, diags, dso, &self.shared_objects, &self.files, target),
741 .archive => |obj| try parseArchive(gpa, io, diags, &self.file_handles, &self.files, target, debug_fmt_strip, default_sym_version, &self.objects, obj, is_static_lib),
742 .dso => |dso| try parseDso(gpa, io, diags, dso, &self.shared_objects, &self.files, target),
742743 }
743744}
744745
......@@ -747,9 +748,10 @@ pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std
747748 defer tracy.end();
748749
749750 const comp = self.base.comp;
751 const io = comp.io;
750752 const diags = &comp.link_diags;
751753
752 if (comp.verbose_link) Compilation.dump_argv(self.dump_argv_list.items);
754 if (comp.verbose_link) try Compilation.dumpArgv(io, self.dump_argv_list.items);
753755
754756 const sub_prog_node = prog_node.start("ELF Flush", 0);
755757 defer sub_prog_node.end();
......@@ -757,7 +759,7 @@ pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std
757759 return flushInner(self, arena, tid) catch |err| switch (err) {
758760 error.OutOfMemory => return error.OutOfMemory,
759761 error.LinkFailure => return error.LinkFailure,
760 else => |e| return diags.fail("ELF flush failed: {s}", .{@errorName(e)}),
762 else => |e| return diags.fail("ELF flush failed: {t}", .{e}),
761763 };
762764}
763765
......@@ -1047,9 +1049,11 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void {
10471049}
10481050
10491051pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {
1050 const diags = &self.base.comp.link_diags;
1051 const obj = link.openObject(path, false, false) catch |err| {
1052 switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) {
1052 const comp = self.base.comp;
1053 const io = comp.io;
1054 const diags = &comp.link_diags;
1055 const obj = link.openObject(io, path, false, false) catch |err| {
1056 switch (diags.failParse(path, "failed to open object: {t}", .{err})) {
10531057 error.LinkFailure => return,
10541058 }
10551059 };
......@@ -1057,10 +1061,11 @@ pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {
10571061}
10581062
10591063fn parseObjectReportingFailure(self: *Elf, obj: link.Input.Object) void {
1060 const diags = &self.base.comp.link_diags;
1064 const comp = self.base.comp;
1065 const diags = &comp.link_diags;
10611066 self.parseObject(obj) catch |err| switch (err) {
10621067 error.LinkFailure => return, // already reported
1063 else => |e| diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}),
1068 else => |e| diags.addParseError(obj.path, "failed to parse object: {t}", .{e}),
10641069 };
10651070}
10661071
......@@ -1068,10 +1073,12 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void {
10681073 const tracy = trace(@src());
10691074 defer tracy.end();
10701075
1071 const gpa = self.base.comp.gpa;
1072 const diags = &self.base.comp.link_diags;
1073 const target = &self.base.comp.root_mod.resolved_target.result;
1074 const debug_fmt_strip = self.base.comp.config.debug_format == .strip;
1076 const comp = self.base.comp;
1077 const io = comp.io;
1078 const gpa = comp.gpa;
1079 const diags = &comp.link_diags;
1080 const target = &comp.root_mod.resolved_target.result;
1081 const debug_fmt_strip = comp.config.debug_format == .strip;
10751082 const default_sym_version = self.default_sym_version;
10761083 const file_handles = &self.file_handles;
10771084
......@@ -1090,14 +1097,15 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void {
10901097 try self.objects.append(gpa, index);
10911098
10921099 const object = self.file(index).?.object;
1093 try object.parseCommon(gpa, diags, obj.path, handle, target);
1100 try object.parseCommon(gpa, io, diags, obj.path, handle, target);
10941101 if (!self.base.isStaticLib()) {
1095 try object.parse(gpa, diags, obj.path, handle, target, debug_fmt_strip, default_sym_version);
1102 try object.parse(gpa, io, diags, obj.path, handle, target, debug_fmt_strip, default_sym_version);
10961103 }
10971104}
10981105
10991106fn parseArchive(
11001107 gpa: Allocator,
1108 io: Io,
11011109 diags: *Diags,
11021110 file_handles: *std.ArrayList(File.Handle),
11031111 files: *std.MultiArrayList(File.Entry),
......@@ -1112,7 +1120,7 @@ fn parseArchive(
11121120 defer tracy.end();
11131121
11141122 const fh = try addFileHandle(gpa, file_handles, obj.file);
1115 var archive = try Archive.parse(gpa, diags, file_handles, obj.path, fh);
1123 var archive = try Archive.parse(gpa, io, diags, file_handles, obj.path, fh);
11161124 defer archive.deinit(gpa);
11171125
11181126 const init_alive = if (is_static_lib) true else obj.must_link;
......@@ -1123,15 +1131,16 @@ fn parseArchive(
11231131 const object = &files.items(.data)[index].object;
11241132 object.index = index;
11251133 object.alive = init_alive;
1126 try object.parseCommon(gpa, diags, obj.path, obj.file, target);
1134 try object.parseCommon(gpa, io, diags, obj.path, obj.file, target);
11271135 if (!is_static_lib)
1128 try object.parse(gpa, diags, obj.path, obj.file, target, debug_fmt_strip, default_sym_version);
1136 try object.parse(gpa, io, diags, obj.path, obj.file, target, debug_fmt_strip, default_sym_version);
11291137 try objects.append(gpa, index);
11301138 }
11311139}
11321140
11331141fn parseDso(
11341142 gpa: Allocator,
1143 io: Io,
11351144 diags: *Diags,
11361145 dso: link.Input.Dso,
11371146 shared_objects: *std.StringArrayHashMapUnmanaged(File.Index),
......@@ -1143,8 +1152,8 @@ fn parseDso(
11431152
11441153 const handle = dso.file;
11451154
1146 const stat = Stat.fromFs(try handle.stat());
1147 var header = try SharedObject.parseHeader(gpa, diags, dso.path, handle, stat, target);
1155 const stat = Stat.fromFs(try handle.stat(io));
1156 var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target);
11481157 defer header.deinit(gpa);
11491158
11501159 const soname = header.soname() orelse dso.path.basename();
......@@ -1158,7 +1167,7 @@ fn parseDso(
11581167
11591168 gop.value_ptr.* = index;
11601169
1161 var parsed = try SharedObject.parse(gpa, &header, handle);
1170 var parsed = try SharedObject.parse(gpa, io, &header, handle);
11621171 errdefer parsed.deinit(gpa);
11631172
11641173 const duped_path: Path = .{
......@@ -2888,13 +2897,7 @@ pub fn allocateAllocSections(self: *Elf) !void {
28882897 if (shdr.sh_offset > 0) {
28892898 // Get size actually commited to the output file.
28902899 const existing_size = self.sectionSize(shndx);
2891 const amt = try self.base.file.?.copyRangeAll(
2892 shdr.sh_offset,
2893 self.base.file.?,
2894 new_offset,
2895 existing_size,
2896 );
2897 if (amt != existing_size) return error.InputOutput;
2900 try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size);
28982901 }
28992902
29002903 shdr.sh_offset = new_offset;
......@@ -2930,13 +2933,7 @@ pub fn allocateNonAllocSections(self: *Elf) !void {
29302933
29312934 if (shdr.sh_offset > 0) {
29322935 const existing_size = self.sectionSize(@intCast(shndx));
2933 const amt = try self.base.file.?.copyRangeAll(
2934 shdr.sh_offset,
2935 self.base.file.?,
2936 new_offset,
2937 existing_size,
2938 );
2939 if (amt != existing_size) return error.InputOutput;
2936 try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size);
29402937 }
29412938
29422939 shdr.sh_offset = new_offset;
......@@ -3649,7 +3646,7 @@ fn fileLookup(files: std.MultiArrayList(File.Entry), index: File.Index, zig_obje
36493646pub fn addFileHandle(
36503647 gpa: Allocator,
36513648 file_handles: *std.ArrayList(File.Handle),
3652 handle: fs.File,
3649 handle: Io.File,
36533650) Allocator.Error!File.HandleIndex {
36543651 try file_handles.append(gpa, handle);
36553652 return @intCast(file_handles.items.len - 1);
......@@ -4066,10 +4063,10 @@ fn fmtDumpState(self: *Elf, writer: *std.Io.Writer) std.Io.Writer.Error!void {
40664063}
40674064
40684065/// Caller owns the memory.
4069pub fn preadAllAlloc(allocator: Allocator, handle: fs.File, offset: u64, size: u64) ![]u8 {
4066pub fn preadAllAlloc(allocator: Allocator, io: Io, io_file: Io.File, offset: u64, size: u64) ![]u8 {
40704067 const buffer = try allocator.alloc(u8, math.cast(usize, size) orelse return error.Overflow);
40714068 errdefer allocator.free(buffer);
4072 const amt = try handle.preadAll(buffer, offset);
4069 const amt = try io_file.readPositionalAll(io, buffer, offset);
40734070 if (amt != size) return error.InputOutput;
40744071 return buffer;
40754072}
......@@ -4435,16 +4432,17 @@ pub fn stringTableLookup(strtab: []const u8, off: u32) [:0]const u8 {
44354432
44364433pub fn pwriteAll(elf_file: *Elf, bytes: []const u8, offset: u64) error{LinkFailure}!void {
44374434 const comp = elf_file.base.comp;
4435 const io = comp.io;
44384436 const diags = &comp.link_diags;
4439 elf_file.base.file.?.pwriteAll(bytes, offset) catch |err| {
4440 return diags.fail("failed to write: {s}", .{@errorName(err)});
4441 };
4437 elf_file.base.file.?.writePositionalAll(io, bytes, offset) catch |err|
4438 return diags.fail("failed to write: {t}", .{err});
44424439}
44434440
4444pub fn setEndPos(elf_file: *Elf, length: u64) error{LinkFailure}!void {
4441pub fn setLength(elf_file: *Elf, length: u64) error{LinkFailure}!void {
44454442 const comp = elf_file.base.comp;
4443 const io = comp.i;
44464444 const diags = &comp.link_diags;
4447 elf_file.base.file.?.setEndPos(length) catch |err| {
4445 elf_file.base.file.?.setLength(io, length) catch |err| {
44484446 return diags.fail("failed to set file end pos: {s}", .{@errorName(err)});
44494447 };
44504448}
......@@ -4458,6 +4456,7 @@ pub fn cast(elf_file: *Elf, comptime T: type, x: anytype) error{LinkFailure}!T {
44584456}
44594457
44604458const std = @import("std");
4459const Io = std.Io;
44614460const build_options = @import("build_options");
44624461const builtin = @import("builtin");
44634462const assert = std.debug.assert;
src/link/Elf/Archive.zig+28-25
......@@ -1,3 +1,21 @@
1const Archive = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const assert = std.debug.assert;
6const elf = std.elf;
7const fs = std.fs;
8const log = std.log.scoped(.link);
9const mem = std.mem;
10const Path = std.Build.Cache.Path;
11const Allocator = std.mem.Allocator;
12
13const Diags = @import("../../link.zig").Diags;
14const Elf = @import("../Elf.zig");
15const File = @import("file.zig").File;
16const Object = @import("Object.zig");
17const StringTable = @import("../StringTable.zig");
18
119objects: []const Object,
220/// '\n'-delimited
321strtab: []const u8,
......@@ -10,22 +28,23 @@ pub fn deinit(a: *Archive, gpa: Allocator) void {
1028
1129pub fn parse(
1230 gpa: Allocator,
31 io: Io,
1332 diags: *Diags,
1433 file_handles: *const std.ArrayList(File.Handle),
1534 path: Path,
1635 handle_index: File.HandleIndex,
1736) !Archive {
18 const handle = file_handles.items[handle_index];
37 const file = file_handles.items[handle_index];
1938 var pos: usize = 0;
2039 {
2140 var magic_buffer: [elf.ARMAG.len]u8 = undefined;
22 const n = try handle.preadAll(&magic_buffer, pos);
41 const n = try file.readPositionalAll(io, &magic_buffer, pos);
2342 if (n != magic_buffer.len) return error.BadMagic;
2443 if (!mem.eql(u8, &magic_buffer, elf.ARMAG)) return error.BadMagic;
2544 pos += magic_buffer.len;
2645 }
2746
28 const size = (try handle.stat()).size;
47 const size = (try file.stat(io)).size;
2948
3049 var objects: std.ArrayList(Object) = .empty;
3150 defer objects.deinit(gpa);
......@@ -36,7 +55,7 @@ pub fn parse(
3655 while (pos < size) {
3756 var hdr: elf.ar_hdr = undefined;
3857 {
39 const n = try handle.preadAll(mem.asBytes(&hdr), pos);
58 const n = try file.readPositionalAll(io, mem.asBytes(&hdr), pos);
4059 if (n != @sizeOf(elf.ar_hdr)) return error.UnexpectedEndOfFile;
4160 }
4261 pos += @sizeOf(elf.ar_hdr);
......@@ -53,7 +72,7 @@ pub fn parse(
5372 if (hdr.isSymtab() or hdr.isSymtab64()) continue;
5473 if (hdr.isStrtab()) {
5574 try strtab.resize(gpa, obj_size);
56 const amt = try handle.preadAll(strtab.items, pos);
75 const amt = try file.readPositionalAll(io, strtab.items, pos);
5776 if (amt != obj_size) return error.InputOutput;
5877 continue;
5978 }
......@@ -120,7 +139,7 @@ pub fn setArHdr(opts: struct {
120139 @memset(mem.asBytes(&hdr), 0x20);
121140
122141 {
123 var writer: std.Io.Writer = .fixed(&hdr.ar_name);
142 var writer: Io.Writer = .fixed(&hdr.ar_name);
124143 switch (opts.name) {
125144 .symtab => writer.print("{s}", .{elf.SYM64NAME}) catch unreachable,
126145 .strtab => writer.print("//", .{}) catch unreachable,
......@@ -133,7 +152,7 @@ pub fn setArHdr(opts: struct {
133152 hdr.ar_gid[0] = '0';
134153 hdr.ar_mode[0] = '0';
135154 {
136 var writer: std.Io.Writer = .fixed(&hdr.ar_size);
155 var writer: Io.Writer = .fixed(&hdr.ar_size);
137156 writer.print("{d}", .{opts.size}) catch unreachable;
138157 }
139158 hdr.ar_fmag = elf.ARFMAG.*;
......@@ -206,7 +225,7 @@ pub const ArSymtab = struct {
206225 ar: ArSymtab,
207226 elf_file: *Elf,
208227
209 fn default(f: Format, writer: *std.Io.Writer) std.Io.Writer.Error!void {
228 fn default(f: Format, writer: *Io.Writer) Io.Writer.Error!void {
210229 const ar = f.ar;
211230 const elf_file = f.elf_file;
212231 for (ar.symtab.items, 0..) |entry, i| {
......@@ -261,7 +280,7 @@ pub const ArStrtab = struct {
261280 try writer.writeAll(ar.buffer.items);
262281 }
263282
264 pub fn format(ar: ArStrtab, writer: *std.Io.Writer) std.Io.Writer.Error!void {
283 pub fn format(ar: ArStrtab, writer: *Io.Writer) Io.Writer.Error!void {
265284 try writer.print("{f}", .{std.ascii.hexEscape(ar.buffer.items, .lower)});
266285 }
267286};
......@@ -277,19 +296,3 @@ pub const ArState = struct {
277296 /// Total size of the contributing object (excludes ar_hdr).
278297 size: u64 = 0,
279298};
280
281const std = @import("std");
282const assert = std.debug.assert;
283const elf = std.elf;
284const fs = std.fs;
285const log = std.log.scoped(.link);
286const mem = std.mem;
287const Path = std.Build.Cache.Path;
288const Allocator = std.mem.Allocator;
289
290const Diags = @import("../../link.zig").Diags;
291const Archive = @This();
292const Elf = @import("../Elf.zig");
293const File = @import("file.zig").File;
294const Object = @import("Object.zig");
295const StringTable = @import("../StringTable.zig");
src/link/Elf/AtomList.zig+8-4
......@@ -90,7 +90,9 @@ pub fn allocate(list: *AtomList, elf_file: *Elf) !void {
9090}
9191
9292pub fn write(list: AtomList, buffer: *std.Io.Writer.Allocating, undefs: anytype, elf_file: *Elf) !void {
93 const gpa = elf_file.base.comp.gpa;
93 const comp = elf_file.base.comp;
94 const gpa = comp.gpa;
95 const io = comp.io;
9496 const osec = elf_file.sections.items(.shdr)[list.output_section_index];
9597 assert(osec.sh_type != elf.SHT_NOBITS);
9698 assert(!list.dirty);
......@@ -121,12 +123,14 @@ pub fn write(list: AtomList, buffer: *std.Io.Writer.Allocating, undefs: anytype,
121123 try atom_ptr.resolveRelocsAlloc(elf_file, out_code);
122124 }
123125
124 try elf_file.base.file.?.pwriteAll(buffer.written(), list.offset(elf_file));
126 try elf_file.base.file.?.writePositionalAll(io, buffer.written(), list.offset(elf_file));
125127 buffer.clearRetainingCapacity();
126128}
127129
128130pub fn writeRelocatable(list: AtomList, buffer: *std.array_list.Managed(u8), elf_file: *Elf) !void {
129 const gpa = elf_file.base.comp.gpa;
131 const comp = elf_file.base.comp;
132 const gpa = comp.gpa;
133 const io = comp.io;
130134 const osec = elf_file.sections.items(.shdr)[list.output_section_index];
131135 assert(osec.sh_type != elf.SHT_NOBITS);
132136
......@@ -152,7 +156,7 @@ pub fn writeRelocatable(list: AtomList, buffer: *std.array_list.Managed(u8), elf
152156 @memcpy(out_code, code);
153157 }
154158
155 try elf_file.base.file.?.pwriteAll(buffer.items, list.offset(elf_file));
159 try elf_file.base.file.?.writePositionalAll(io, buffer.items, list.offset(elf_file));
156160 buffer.clearRetainingCapacity();
157161}
158162
src/link/Elf/Object.zig+61-51
......@@ -1,3 +1,30 @@
1const Object = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const assert = std.debug.assert;
6const eh_frame = @import("eh_frame.zig");
7const elf = std.elf;
8const fs = std.fs;
9const log = std.log.scoped(.link);
10const math = std.math;
11const mem = std.mem;
12const Path = std.Build.Cache.Path;
13const Allocator = std.mem.Allocator;
14
15const Diags = @import("../../link.zig").Diags;
16const Archive = @import("Archive.zig");
17const Atom = @import("Atom.zig");
18const AtomList = @import("AtomList.zig");
19const Cie = eh_frame.Cie;
20const Elf = @import("../Elf.zig");
21const Fde = eh_frame.Fde;
22const File = @import("file.zig").File;
23const Merge = @import("Merge.zig");
24const Symbol = @import("Symbol.zig");
25const Alignment = Atom.Alignment;
26const riscv = @import("../riscv.zig");
27
128archive: ?InArchive = null,
229/// Archive files cannot contain subdirectories, so only the basename is needed
330/// for output. However, the full path is kept for error reporting.
......@@ -65,10 +92,11 @@ pub fn deinit(self: *Object, gpa: Allocator) void {
6592pub fn parse(
6693 self: *Object,
6794 gpa: Allocator,
95 io: Io,
6896 diags: *Diags,
6997 /// For error reporting purposes only.
7098 path: Path,
71 handle: fs.File,
99 handle: Io.File,
72100 target: *const std.Target,
73101 debug_fmt_strip: bool,
74102 default_sym_version: elf.Versym,
......@@ -78,7 +106,7 @@ pub fn parse(
78106 // Allocate atom index 0 to null atom
79107 try self.atoms.append(gpa, .{ .extra_index = try self.addAtomExtra(gpa, .{}) });
80108
81 try self.initAtoms(gpa, diags, path, handle, debug_fmt_strip, target);
109 try self.initAtoms(gpa, io, diags, path, handle, debug_fmt_strip, target);
82110 try self.initSymbols(gpa, default_sym_version);
83111
84112 for (self.shdrs.items, 0..) |shdr, i| {
......@@ -87,7 +115,7 @@ pub fn parse(
87115 if ((target.cpu.arch == .x86_64 and shdr.sh_type == elf.SHT_X86_64_UNWIND) or
88116 mem.eql(u8, self.getString(atom_ptr.name_offset), ".eh_frame"))
89117 {
90 try self.parseEhFrame(gpa, handle, @intCast(i), target);
118 try self.parseEhFrame(gpa, io, handle, @intCast(i), target);
91119 }
92120 }
93121}
......@@ -95,15 +123,16 @@ pub fn parse(
95123pub fn parseCommon(
96124 self: *Object,
97125 gpa: Allocator,
126 io: Io,
98127 diags: *Diags,
99128 path: Path,
100 handle: fs.File,
129 handle: Io.File,
101130 target: *const std.Target,
102131) !void {
103132 const offset = if (self.archive) |ar| ar.offset else 0;
104 const file_size = (try handle.stat()).size;
133 const file_size = (try handle.stat(io)).size;
105134
106 const header_buffer = try Elf.preadAllAlloc(gpa, handle, offset, @sizeOf(elf.Elf64_Ehdr));
135 const header_buffer = try Elf.preadAllAlloc(gpa, io, handle, offset, @sizeOf(elf.Elf64_Ehdr));
107136 defer gpa.free(header_buffer);
108137 self.header = @as(*align(1) const elf.Elf64_Ehdr, @ptrCast(header_buffer)).*;
109138 if (!mem.eql(u8, self.header.?.e_ident[0..4], elf.MAGIC)) {
......@@ -127,7 +156,7 @@ pub fn parseCommon(
127156 return diags.failParse(path, "corrupt header: section header table extends past the end of file", .{});
128157 }
129158
130 const shdrs_buffer = try Elf.preadAllAlloc(gpa, handle, offset + shoff, shsize);
159 const shdrs_buffer = try Elf.preadAllAlloc(gpa, io, handle, offset + shoff, shsize);
131160 defer gpa.free(shdrs_buffer);
132161 const shdrs = @as([*]align(1) const elf.Elf64_Shdr, @ptrCast(shdrs_buffer.ptr))[0..shnum];
133162 try self.shdrs.appendUnalignedSlice(gpa, shdrs);
......@@ -140,7 +169,7 @@ pub fn parseCommon(
140169 }
141170 }
142171
143 const shstrtab = try self.preadShdrContentsAlloc(gpa, handle, self.header.?.e_shstrndx);
172 const shstrtab = try self.preadShdrContentsAlloc(gpa, io, handle, self.header.?.e_shstrndx);
144173 defer gpa.free(shstrtab);
145174 for (self.shdrs.items) |shdr| {
146175 if (shdr.sh_name >= shstrtab.len) {
......@@ -158,7 +187,7 @@ pub fn parseCommon(
158187 const shdr = self.shdrs.items[index];
159188 self.first_global = shdr.sh_info;
160189
161 const raw_symtab = try self.preadShdrContentsAlloc(gpa, handle, index);
190 const raw_symtab = try self.preadShdrContentsAlloc(gpa, io, handle, index);
162191 defer gpa.free(raw_symtab);
163192 const nsyms = math.divExact(usize, raw_symtab.len, @sizeOf(elf.Elf64_Sym)) catch {
164193 return diags.failParse(path, "symbol table not evenly divisible", .{});
......@@ -166,7 +195,7 @@ pub fn parseCommon(
166195 const symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(raw_symtab.ptr))[0..nsyms];
167196
168197 const strtab_bias = @as(u32, @intCast(self.strtab.items.len));
169 const strtab = try self.preadShdrContentsAlloc(gpa, handle, shdr.sh_link);
198 const strtab = try self.preadShdrContentsAlloc(gpa, io, handle, shdr.sh_link);
170199 defer gpa.free(strtab);
171200 try self.strtab.appendSlice(gpa, strtab);
172201
......@@ -262,9 +291,10 @@ pub fn validateEFlags(
262291fn initAtoms(
263292 self: *Object,
264293 gpa: Allocator,
294 io: Io,
265295 diags: *Diags,
266296 path: Path,
267 handle: fs.File,
297 handle: Io.File,
268298 debug_fmt_strip: bool,
269299 target: *const std.Target,
270300) !void {
......@@ -297,7 +327,7 @@ fn initAtoms(
297327 };
298328
299329 const shndx: u32 = @intCast(i);
300 const group_raw_data = try self.preadShdrContentsAlloc(gpa, handle, shndx);
330 const group_raw_data = try self.preadShdrContentsAlloc(gpa, io, handle, shndx);
301331 defer gpa.free(group_raw_data);
302332 const group_nmembers = math.divExact(usize, group_raw_data.len, @sizeOf(u32)) catch {
303333 return diags.failParse(path, "corrupt section group: not evenly divisible ", .{});
......@@ -338,7 +368,7 @@ fn initAtoms(
338368 const shndx: u32 = @intCast(i);
339369 if (self.skipShdr(shndx, debug_fmt_strip)) continue;
340370 const size, const alignment = if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) blk: {
341 const data = try self.preadShdrContentsAlloc(gpa, handle, shndx);
371 const data = try self.preadShdrContentsAlloc(gpa, io, handle, shndx);
342372 defer gpa.free(data);
343373 const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;
344374 break :blk .{ chdr.ch_size, Alignment.fromNonzeroByteUnits(chdr.ch_addralign) };
......@@ -359,7 +389,7 @@ fn initAtoms(
359389 elf.SHT_REL, elf.SHT_RELA => {
360390 const atom_index = self.atoms_indexes.items[shdr.sh_info];
361391 if (self.atom(atom_index)) |atom_ptr| {
362 const relocs = try self.preadRelocsAlloc(gpa, handle, @intCast(i));
392 const relocs = try self.preadRelocsAlloc(gpa, io, handle, @intCast(i));
363393 defer gpa.free(relocs);
364394 atom_ptr.relocs_section_index = @intCast(i);
365395 const rel_index: u32 = @intCast(self.relocs.items.len);
......@@ -421,7 +451,8 @@ fn initSymbols(
421451fn parseEhFrame(
422452 self: *Object,
423453 gpa: Allocator,
424 handle: fs.File,
454 io: Io,
455 handle: Io.File,
425456 shndx: u32,
426457 target: *const std.Target,
427458) !void {
......@@ -430,12 +461,12 @@ fn parseEhFrame(
430461 else => {},
431462 } else null;
432463
433 const raw = try self.preadShdrContentsAlloc(gpa, handle, shndx);
464 const raw = try self.preadShdrContentsAlloc(gpa, io, handle, shndx);
434465 defer gpa.free(raw);
435466 const data_start: u32 = @intCast(self.eh_frame_data.items.len);
436467 try self.eh_frame_data.appendSlice(gpa, raw);
437468 const relocs = if (relocs_shndx) |index|
438 try self.preadRelocsAlloc(gpa, handle, index)
469 try self.preadRelocsAlloc(gpa, io, handle, index)
439470 else
440471 &[0]elf.Elf64_Rela{};
441472 defer gpa.free(relocs);
......@@ -1095,13 +1126,18 @@ pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, elf_file: *Elf
10951126}
10961127
10971128pub fn updateArSize(self: *Object, elf_file: *Elf) !void {
1129 const comp = elf_file.base.comp;
1130 const io = comp.io;
10981131 self.output_ar_state.size = if (self.archive) |ar| ar.size else size: {
10991132 const handle = elf_file.fileHandle(self.file_handle);
1100 break :size (try handle.stat()).size;
1133 break :size (try handle.stat(io)).size;
11011134 };
11021135}
11031136
11041137pub fn writeAr(self: Object, elf_file: *Elf, writer: anytype) !void {
1138 const comp = elf_file.base.comp;
1139 const gpa = comp.gpa;
1140 const io = comp.io;
11051141 const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow;
11061142 const offset: u64 = if (self.archive) |ar| ar.offset else 0;
11071143 const name = fs.path.basename(self.path.sub_path);
......@@ -1114,10 +1150,9 @@ pub fn writeAr(self: Object, elf_file: *Elf, writer: anytype) !void {
11141150 });
11151151 try writer.writeAll(mem.asBytes(&hdr));
11161152 const handle = elf_file.fileHandle(self.file_handle);
1117 const gpa = elf_file.base.comp.gpa;
11181153 const data = try gpa.alloc(u8, size);
11191154 defer gpa.free(data);
1120 const amt = try handle.preadAll(data, offset);
1155 const amt = try handle.readPositionalAll(io, data, offset);
11211156 if (amt != size) return error.InputOutput;
11221157 try writer.writeAll(data);
11231158}
......@@ -1190,11 +1225,12 @@ pub fn writeSymtab(self: *Object, elf_file: *Elf) void {
11901225/// Caller owns the memory.
11911226pub fn codeDecompressAlloc(self: *Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 {
11921227 const comp = elf_file.base.comp;
1228 const io = comp.io;
11931229 const gpa = comp.gpa;
11941230 const atom_ptr = self.atom(atom_index).?;
11951231 const shdr = atom_ptr.inputShdr(elf_file);
11961232 const handle = elf_file.fileHandle(self.file_handle);
1197 const data = try self.preadShdrContentsAlloc(gpa, handle, atom_ptr.input_section_index);
1233 const data = try self.preadShdrContentsAlloc(gpa, io, handle, atom_ptr.input_section_index);
11981234 defer if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) gpa.free(data);
11991235
12001236 if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) {
......@@ -1310,18 +1346,18 @@ fn addString(self: *Object, gpa: Allocator, str: []const u8) !u32 {
13101346}
13111347
13121348/// Caller owns the memory.
1313fn preadShdrContentsAlloc(self: Object, gpa: Allocator, handle: fs.File, index: u32) ![]u8 {
1349fn preadShdrContentsAlloc(self: Object, gpa: Allocator, io: Io, handle: Io.File, index: u32) ![]u8 {
13141350 assert(index < self.shdrs.items.len);
13151351 const offset = if (self.archive) |ar| ar.offset else 0;
13161352 const shdr = self.shdrs.items[index];
13171353 const sh_offset = math.cast(u64, shdr.sh_offset) orelse return error.Overflow;
13181354 const sh_size = math.cast(u64, shdr.sh_size) orelse return error.Overflow;
1319 return Elf.preadAllAlloc(gpa, handle, offset + sh_offset, sh_size);
1355 return Elf.preadAllAlloc(gpa, io, handle, offset + sh_offset, sh_size);
13201356}
13211357
13221358/// Caller owns the memory.
1323fn preadRelocsAlloc(self: Object, gpa: Allocator, handle: fs.File, shndx: u32) ![]align(1) const elf.Elf64_Rela {
1324 const raw = try self.preadShdrContentsAlloc(gpa, handle, shndx);
1359fn preadRelocsAlloc(self: Object, gpa: Allocator, io: Io, handle: Io.File, shndx: u32) ![]align(1) const elf.Elf64_Rela {
1360 const raw = try self.preadShdrContentsAlloc(gpa, io, handle, shndx);
13251361 const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela));
13261362 return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num];
13271363}
......@@ -1552,29 +1588,3 @@ const InArchive = struct {
15521588 offset: u64,
15531589 size: u32,
15541590};
1555
1556const Object = @This();
1557
1558const std = @import("std");
1559const assert = std.debug.assert;
1560const eh_frame = @import("eh_frame.zig");
1561const elf = std.elf;
1562const fs = std.fs;
1563const log = std.log.scoped(.link);
1564const math = std.math;
1565const mem = std.mem;
1566const Path = std.Build.Cache.Path;
1567const Allocator = std.mem.Allocator;
1568
1569const Diags = @import("../../link.zig").Diags;
1570const Archive = @import("Archive.zig");
1571const Atom = @import("Atom.zig");
1572const AtomList = @import("AtomList.zig");
1573const Cie = eh_frame.Cie;
1574const Elf = @import("../Elf.zig");
1575const Fde = eh_frame.Fde;
1576const File = @import("file.zig").File;
1577const Merge = @import("Merge.zig");
1578const Symbol = @import("Symbol.zig");
1579const Alignment = Atom.Alignment;
1580const riscv = @import("../riscv.zig");
src/link/Elf/SharedObject.zig+28-25
......@@ -1,3 +1,20 @@
1const SharedObject = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const assert = std.debug.assert;
6const elf = std.elf;
7const log = std.log.scoped(.elf);
8const mem = std.mem;
9const Path = std.Build.Cache.Path;
10const Stat = std.Build.Cache.File.Stat;
11const Allocator = mem.Allocator;
12
13const Elf = @import("../Elf.zig");
14const File = @import("file.zig").File;
15const Symbol = @import("Symbol.zig");
16const Diags = @import("../../link.zig").Diags;
17
118path: Path,
219index: File.Index,
320
......@@ -92,16 +109,17 @@ pub const Parsed = struct {
92109
93110pub fn parseHeader(
94111 gpa: Allocator,
112 io: Io,
95113 diags: *Diags,
96114 file_path: Path,
97 fs_file: std.fs.File,
115 file: Io.File,
98116 stat: Stat,
99117 target: *const std.Target,
100118) !Header {
101119 var ehdr: elf.Elf64_Ehdr = undefined;
102120 {
103121 const buf = mem.asBytes(&ehdr);
104 const amt = try fs_file.preadAll(buf, 0);
122 const amt = try file.readPositionalAll(io, buf, 0);
105123 if (amt != buf.len) return error.UnexpectedEndOfFile;
106124 }
107125 if (!mem.eql(u8, ehdr.e_ident[0..4], "\x7fELF")) return error.BadMagic;
......@@ -118,7 +136,7 @@ pub fn parseHeader(
118136 errdefer gpa.free(sections);
119137 {
120138 const buf = mem.sliceAsBytes(sections);
121 const amt = try fs_file.preadAll(buf, shoff);
139 const amt = try file.readPositionalAll(io, buf, shoff);
122140 if (amt != buf.len) return error.UnexpectedEndOfFile;
123141 }
124142
......@@ -143,7 +161,7 @@ pub fn parseHeader(
143161 const dynamic_table = try gpa.alloc(elf.Elf64_Dyn, n);
144162 errdefer gpa.free(dynamic_table);
145163 const buf = mem.sliceAsBytes(dynamic_table);
146 const amt = try fs_file.preadAll(buf, shdr.sh_offset);
164 const amt = try file.readPositionalAll(io, buf, shdr.sh_offset);
147165 if (amt != buf.len) return error.UnexpectedEndOfFile;
148166 break :dt dynamic_table;
149167 } else &.{};
......@@ -158,7 +176,7 @@ pub fn parseHeader(
158176 const strtab_shdr = sections[dynsym_shdr.sh_link];
159177 const n = std.math.cast(usize, strtab_shdr.sh_size) orelse return error.Overflow;
160178 const buf = try strtab.addManyAsSlice(gpa, n);
161 const amt = try fs_file.preadAll(buf, strtab_shdr.sh_offset);
179 const amt = try file.readPositionalAll(io, buf, strtab_shdr.sh_offset);
162180 if (amt != buf.len) return error.UnexpectedEndOfFile;
163181 }
164182
......@@ -190,9 +208,10 @@ pub fn parseHeader(
190208
191209pub fn parse(
192210 gpa: Allocator,
211 io: Io,
193212 /// Moves resources from header. Caller may unconditionally deinit.
194213 header: *Header,
195 fs_file: std.fs.File,
214 file: Io.File,
196215) !Parsed {
197216 const symtab = if (header.dynsym_sect_index) |index| st: {
198217 const shdr = header.sections[index];
......@@ -200,7 +219,7 @@ pub fn parse(
200219 const symtab = try gpa.alloc(elf.Elf64_Sym, n);
201220 errdefer gpa.free(symtab);
202221 const buf = mem.sliceAsBytes(symtab);
203 const amt = try fs_file.preadAll(buf, shdr.sh_offset);
222 const amt = try file.readPositionalAll(io, buf, shdr.sh_offset);
204223 if (amt != buf.len) return error.UnexpectedEndOfFile;
205224 break :st symtab;
206225 } else &.{};
......@@ -211,7 +230,7 @@ pub fn parse(
211230
212231 if (header.verdef_sect_index) |shndx| {
213232 const shdr = header.sections[shndx];
214 const verdefs = try Elf.preadAllAlloc(gpa, fs_file, shdr.sh_offset, shdr.sh_size);
233 const verdefs = try Elf.preadAllAlloc(gpa, io, file, shdr.sh_offset, shdr.sh_size);
215234 defer gpa.free(verdefs);
216235
217236 var offset: u32 = 0;
......@@ -237,7 +256,7 @@ pub fn parse(
237256 const versyms = try gpa.alloc(elf.Versym, symtab.len);
238257 errdefer gpa.free(versyms);
239258 const buf = mem.sliceAsBytes(versyms);
240 const amt = try fs_file.preadAll(buf, shdr.sh_offset);
259 const amt = try file.readPositionalAll(io, buf, shdr.sh_offset);
241260 if (amt != buf.len) return error.UnexpectedEndOfFile;
242261 break :vs versyms;
243262 } else &.{};
......@@ -534,19 +553,3 @@ const Format = struct {
534553 }
535554 }
536555};
537
538const SharedObject = @This();
539
540const std = @import("std");
541const assert = std.debug.assert;
542const elf = std.elf;
543const log = std.log.scoped(.elf);
544const mem = std.mem;
545const Path = std.Build.Cache.Path;
546const Stat = std.Build.Cache.File.Stat;
547const Allocator = mem.Allocator;
548
549const Elf = @import("../Elf.zig");
550const File = @import("file.zig").File;
551const Symbol = @import("Symbol.zig");
552const Diags = @import("../../link.zig").Diags;
src/link/Elf/ZigObject.zig+19-9
......@@ -740,7 +740,9 @@ pub fn checkDuplicates(self: *ZigObject, dupes: anytype, elf_file: *Elf) error{O
740740/// We need this so that we can write to an archive.
741741/// TODO implement writing ZigObject data directly to a buffer instead.
742742pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void {
743 const gpa = elf_file.base.comp.gpa;
743 const comp = elf_file.base.comp;
744 const gpa = comp.gpa;
745 const io = comp.io;
744746 const shsize: u64 = switch (elf_file.ptr_width) {
745747 .p32 => @sizeOf(elf.Elf32_Shdr),
746748 .p64 => @sizeOf(elf.Elf64_Shdr),
......@@ -753,7 +755,7 @@ pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void {
753755 const size = std.math.cast(usize, end_pos) orelse return error.Overflow;
754756 try self.data.resize(gpa, size);
755757
756 const amt = try elf_file.base.file.?.preadAll(self.data.items, 0);
758 const amt = try elf_file.base.file.?.readPositionalAll(io, self.data.items, 0);
757759 if (amt != size) return error.InputOutput;
758760}
759761
......@@ -901,13 +903,15 @@ pub fn writeSymtab(self: ZigObject, elf_file: *Elf) void {
901903/// Returns atom's code.
902904/// Caller owns the memory.
903905pub fn codeAlloc(self: *ZigObject, elf_file: *Elf, atom_index: Atom.Index) ![]u8 {
904 const gpa = elf_file.base.comp.gpa;
906 const comp = elf_file.base.comp;
907 const gpa = comp.gpa;
908 const io = comp.io;
905909 const atom_ptr = self.atom(atom_index).?;
906910 const file_offset = atom_ptr.offset(elf_file);
907911 const size = std.math.cast(usize, atom_ptr.size) orelse return error.Overflow;
908912 const code = try gpa.alloc(u8, size);
909913 errdefer gpa.free(code);
910 const amt = try elf_file.base.file.?.preadAll(code, file_offset);
914 const amt = try elf_file.base.file.?.readPositionalAll(io, code, file_offset);
911915 if (amt != code.len) {
912916 log.err("fetching code for {s} failed", .{atom_ptr.name(elf_file)});
913917 return error.InputOutput;
......@@ -1365,6 +1369,8 @@ fn updateNavCode(
13651369) link.File.UpdateNavError!void {
13661370 const zcu = pt.zcu;
13671371 const gpa = zcu.gpa;
1372 const comp = elf_file.base.comp;
1373 const io = comp.io;
13681374 const ip = &zcu.intern_pool;
13691375 const nav = ip.getNav(nav_index);
13701376
......@@ -1449,8 +1455,8 @@ fn updateNavCode(
14491455 const shdr = elf_file.sections.items(.shdr)[shdr_index];
14501456 if (shdr.sh_type != elf.SHT_NOBITS) {
14511457 const file_offset = atom_ptr.offset(elf_file);
1452 elf_file.base.file.?.pwriteAll(code, file_offset) catch |err|
1453 return elf_file.base.cgFail(nav_index, "failed to write to output file: {s}", .{@errorName(err)});
1458 elf_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err|
1459 return elf_file.base.cgFail(nav_index, "failed to write to output file: {t}", .{err});
14541460 log.debug("writing {f} from 0x{x} to 0x{x}", .{ nav.fqn.fmt(ip), file_offset, file_offset + code.len });
14551461 }
14561462}
......@@ -1467,6 +1473,8 @@ fn updateTlv(
14671473 const zcu = pt.zcu;
14681474 const ip = &zcu.intern_pool;
14691475 const gpa = zcu.gpa;
1476 const comp = elf_file.base.comp;
1477 const io = comp.io;
14701478 const nav = ip.getNav(nav_index);
14711479
14721480 log.debug("updateTlv {f}({d})", .{ nav.fqn.fmt(ip), nav_index });
......@@ -1503,8 +1511,8 @@ fn updateTlv(
15031511 const shdr = elf_file.sections.items(.shdr)[shndx];
15041512 if (shdr.sh_type != elf.SHT_NOBITS) {
15051513 const file_offset = atom_ptr.offset(elf_file);
1506 elf_file.base.file.?.pwriteAll(code, file_offset) catch |err|
1507 return elf_file.base.cgFail(nav_index, "failed to write to output file: {s}", .{@errorName(err)});
1514 elf_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err|
1515 return elf_file.base.cgFail(nav_index, "failed to write to output file: {t}", .{err});
15081516 log.debug("writing TLV {s} from 0x{x} to 0x{x}", .{
15091517 atom_ptr.name(elf_file),
15101518 file_offset,
......@@ -2003,6 +2011,8 @@ fn trampolineSize(cpu_arch: std.Target.Cpu.Arch) u64 {
20032011}
20042012
20052013fn writeTrampoline(tr_sym: Symbol, target: Symbol, elf_file: *Elf) !void {
2014 const comp = elf_file.base.comp;
2015 const io = comp.io;
20062016 const atom_ptr = tr_sym.atom(elf_file).?;
20072017 const fileoff = atom_ptr.offset(elf_file);
20082018 const source_addr = tr_sym.address(.{}, elf_file);
......@@ -2012,7 +2022,7 @@ fn writeTrampoline(tr_sym: Symbol, target: Symbol, elf_file: *Elf) !void {
20122022 .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf),
20132023 else => @panic("TODO implement write trampoline for this CPU arch"),
20142024 };
2015 try elf_file.base.file.?.pwriteAll(out, fileoff);
2025 try elf_file.base.file.?.writePositionalAll(io, out, fileoff);
20162026
20172027 if (elf_file.base.child_pid) |pid| {
20182028 switch (builtin.os.tag) {
src/link/Elf/file.zig+18-17
......@@ -1,3 +1,20 @@
1const std = @import("std");
2const Io = std.Io;
3const elf = std.elf;
4const log = std.log.scoped(.link);
5const Path = std.Build.Cache.Path;
6const Allocator = std.mem.Allocator;
7
8const Archive = @import("Archive.zig");
9const Atom = @import("Atom.zig");
10const Cie = @import("eh_frame.zig").Cie;
11const Elf = @import("../Elf.zig");
12const LinkerDefined = @import("LinkerDefined.zig");
13const Object = @import("Object.zig");
14const SharedObject = @import("SharedObject.zig");
15const Symbol = @import("Symbol.zig");
16const ZigObject = @import("ZigObject.zig");
17
118pub const File = union(enum) {
219 zig_object: *ZigObject,
320 linker_defined: *LinkerDefined,
......@@ -279,22 +296,6 @@ pub const File = union(enum) {
279296 shared_object: SharedObject,
280297 };
281298
282 pub const Handle = std.fs.File;
299 pub const Handle = Io.File;
283300 pub const HandleIndex = Index;
284301};
285
286const std = @import("std");
287const elf = std.elf;
288const log = std.log.scoped(.link);
289const Path = std.Build.Cache.Path;
290const Allocator = std.mem.Allocator;
291
292const Archive = @import("Archive.zig");
293const Atom = @import("Atom.zig");
294const Cie = @import("eh_frame.zig").Cie;
295const Elf = @import("../Elf.zig");
296const LinkerDefined = @import("LinkerDefined.zig");
297const Object = @import("Object.zig");
298const SharedObject = @import("SharedObject.zig");
299const Symbol = @import("Symbol.zig");
300const ZigObject = @import("ZigObject.zig");
src/link/Elf/relocatable.zig+34-34
......@@ -1,5 +1,26 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const elf = std.elf;
4const math = std.math;
5const mem = std.mem;
6const Path = std.Build.Cache.Path;
7const log = std.log.scoped(.link);
8const state_log = std.log.scoped(.link_state);
9
10const build_options = @import("build_options");
11
12const eh_frame = @import("eh_frame.zig");
13const link = @import("../../link.zig");
14const Archive = @import("Archive.zig");
15const Compilation = @import("../../Compilation.zig");
16const Elf = @import("../Elf.zig");
17const File = @import("file.zig").File;
18const Object = @import("Object.zig");
19const Symbol = @import("Symbol.zig");
20
121pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void {
222 const gpa = comp.gpa;
23 const io = comp.io;
324 const diags = &comp.link_diags;
425
526 if (diags.hasErrors()) return error.LinkFailure;
......@@ -125,8 +146,8 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void {
125146
126147 assert(writer.buffered().len == total_size);
127148
128 try elf_file.base.file.?.setEndPos(total_size);
129 try elf_file.base.file.?.pwriteAll(writer.buffered(), 0);
149 try elf_file.base.file.?.setLength(io, total_size);
150 try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), 0);
130151
131152 if (diags.hasErrors()) return error.LinkFailure;
132153}
......@@ -330,13 +351,7 @@ fn allocateAllocSections(elf_file: *Elf) !void {
330351
331352 if (shdr.sh_offset > 0) {
332353 const existing_size = elf_file.sectionSize(@intCast(shndx));
333 const amt = try elf_file.base.file.?.copyRangeAll(
334 shdr.sh_offset,
335 elf_file.base.file.?,
336 new_offset,
337 existing_size,
338 );
339 if (amt != existing_size) return error.InputOutput;
354 try elf_file.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size);
340355 }
341356
342357 shdr.sh_offset = new_offset;
......@@ -360,7 +375,9 @@ fn writeAtoms(elf_file: *Elf) !void {
360375}
361376
362377fn writeSyntheticSections(elf_file: *Elf) !void {
363 const gpa = elf_file.base.comp.gpa;
378 const comp = elf_file.base.comp;
379 const io = comp.io;
380 const gpa = comp.gpa;
364381 const slice = elf_file.sections.slice();
365382
366383 const SortRelocs = struct {
......@@ -397,7 +414,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void {
397414 shdr.sh_offset + shdr.sh_size,
398415 });
399416
400 try elf_file.base.file.?.pwriteAll(@ptrCast(relocs.items), shdr.sh_offset);
417 try elf_file.base.file.?.writePositionalAll(io, @ptrCast(relocs.items), shdr.sh_offset);
401418 }
402419
403420 if (elf_file.section_indexes.eh_frame) |shndx| {
......@@ -417,7 +434,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void {
417434 shdr.sh_offset + sh_size,
418435 });
419436 assert(writer.buffered().len == sh_size - existing_size);
420 try elf_file.base.file.?.pwriteAll(writer.buffered(), shdr.sh_offset + existing_size);
437 try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), shdr.sh_offset + existing_size);
421438 }
422439 if (elf_file.section_indexes.eh_frame_rela) |shndx| {
423440 const shdr = slice.items(.shdr)[shndx];
......@@ -435,7 +452,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void {
435452 shdr.sh_offset,
436453 shdr.sh_offset + shdr.sh_size,
437454 });
438 try elf_file.base.file.?.pwriteAll(@ptrCast(relocs.items), shdr.sh_offset);
455 try elf_file.base.file.?.writePositionalAll(io, @ptrCast(relocs.items), shdr.sh_offset);
439456 }
440457
441458 try writeGroups(elf_file);
......@@ -444,7 +461,9 @@ fn writeSyntheticSections(elf_file: *Elf) !void {
444461}
445462
446463fn writeGroups(elf_file: *Elf) !void {
447 const gpa = elf_file.base.comp.gpa;
464 const comp = elf_file.base.comp;
465 const io = comp.io;
466 const gpa = comp.gpa;
448467 for (elf_file.group_sections.items) |cgs| {
449468 const shdr = elf_file.sections.items(.shdr)[cgs.shndx];
450469 const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow;
......@@ -457,25 +476,6 @@ fn writeGroups(elf_file: *Elf) !void {
457476 shdr.sh_offset,
458477 shdr.sh_offset + shdr.sh_size,
459478 });
460 try elf_file.base.file.?.pwriteAll(writer.buffered(), shdr.sh_offset);
479 try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), shdr.sh_offset);
461480 }
462481}
463
464const assert = std.debug.assert;
465const build_options = @import("build_options");
466const eh_frame = @import("eh_frame.zig");
467const elf = std.elf;
468const link = @import("../../link.zig");
469const log = std.log.scoped(.link);
470const math = std.math;
471const mem = std.mem;
472const state_log = std.log.scoped(.link_state);
473const Path = std.Build.Cache.Path;
474const std = @import("std");
475
476const Archive = @import("Archive.zig");
477const Compilation = @import("../../Compilation.zig");
478const Elf = @import("../Elf.zig");
479const File = @import("file.zig").File;
480const Object = @import("Object.zig");
481const Symbol = @import("Symbol.zig");
src/link/Elf2.zig+45-33
......@@ -1,3 +1,23 @@
1const Elf = @This();
2
3const builtin = @import("builtin");
4const native_endian = builtin.cpu.arch.endian();
5
6const std = @import("std");
7const Io = std.Io;
8const assert = std.debug.assert;
9const log = std.log.scoped(.link);
10
11const codegen = @import("../codegen.zig");
12const Compilation = @import("../Compilation.zig");
13const InternPool = @import("../InternPool.zig");
14const link = @import("../link.zig");
15const MappedFile = @import("MappedFile.zig");
16const target_util = @import("../target.zig");
17const Type = @import("../Type.zig");
18const Value = @import("../Value.zig");
19const Zcu = @import("../Zcu.zig");
20
121base: link.File,
222options: link.File.OpenOptions,
323mf: MappedFile,
......@@ -908,6 +928,7 @@ fn create(
908928 path: std.Build.Cache.Path,
909929 options: link.File.OpenOptions,
910930) !*Elf {
931 const io = comp.io;
911932 const target = &comp.root_mod.resolved_target.result;
912933 assert(target.ofmt == .elf);
913934 const class: std.elf.CLASS = switch (target.ptrBitWidth()) {
......@@ -953,11 +974,11 @@ fn create(
953974 };
954975
955976 const elf = try arena.create(Elf);
956 const file = try path.root_dir.handle.adaptToNewApi().createFile(comp.io, path.sub_path, .{
977 const file = try path.root_dir.handle.createFile(io, path.sub_path, .{
957978 .read = true,
958 .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode),
979 .permissions = link.File.determinePermissions(comp.config.output_mode, comp.config.link_mode),
959980 });
960 errdefer file.close(comp.io);
981 errdefer file.close(io);
961982 elf.* = .{
962983 .base = .{
963984 .tag = .elf2,
......@@ -965,7 +986,7 @@ fn create(
965986 .comp = comp,
966987 .emit = path,
967988
968 .file = .adaptFromNewApi(file),
989 .file = file,
969990 .gc_sections = false,
970991 .print_gc_sections = false,
971992 .build_id = .none,
......@@ -973,7 +994,7 @@ fn create(
973994 .stack_size = 0,
974995 },
975996 .options = options,
976 .mf = try .init(file, comp.gpa),
997 .mf = try .init(file, comp.gpa, io),
977998 .ni = .{
978999 .tls = .none,
9791000 },
......@@ -1973,8 +1994,8 @@ pub fn lazySymbol(elf: *Elf, lazy: link.File.LazySymbol) !Symbol.Index {
19731994 return lazy_gop.value_ptr.*;
19741995}
19751996
1976pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError ||
1977 std.Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, BadMagic, LinkFailure })!void {
1997pub fn loadInput(elf: *Elf, input: link.Input) (Io.File.Reader.SizeError ||
1998 Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, BadMagic, LinkFailure })!void {
19781999 const io = elf.base.comp.io;
19792000 var buf: [4096]u8 = undefined;
19802001 switch (input) {
......@@ -2007,7 +2028,7 @@ pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError ||
20072028 .dso_exact => |dso_exact| try elf.loadDsoExact(dso_exact.name),
20082029 }
20092030}
2010fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !void {
2031fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
20112032 const comp = elf.base.comp;
20122033 const gpa = comp.gpa;
20132034 const diags = &comp.link_diags;
......@@ -2067,7 +2088,7 @@ fn loadObject(
20672088 elf: *Elf,
20682089 path: std.Build.Cache.Path,
20692090 member: ?[]const u8,
2070 fr: *std.Io.File.Reader,
2091 fr: *Io.File.Reader,
20712092 fl: MappedFile.Node.FileLocation,
20722093) !void {
20732094 const comp = elf.base.comp;
......@@ -2310,7 +2331,7 @@ fn loadObject(
23102331 },
23112332 }
23122333}
2313fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *std.Io.File.Reader) !void {
2334fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
23142335 const comp = elf.base.comp;
23152336 const diags = &comp.link_diags;
23162337 const r = &fr.interface;
......@@ -3305,12 +3326,13 @@ fn flushInputSection(elf: *Elf, isi: Node.InputSectionIndex) !void {
33053326 const file_loc = isi.fileLocation(elf);
33063327 if (file_loc.size == 0) return;
33073328 const comp = elf.base.comp;
3329 const io = comp.io;
33083330 const gpa = comp.gpa;
33093331 const ii = isi.input(elf);
33103332 const path = ii.path(elf);
3311 const file = try path.root_dir.handle.adaptToNewApi().openFile(comp.io, path.sub_path, .{});
3312 defer file.close(comp.io);
3313 var fr = file.reader(comp.io, &.{});
3333 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
3334 defer file.close(io);
3335 var fr = file.reader(io, &.{});
33143336 try fr.seekTo(file_loc.offset);
33153337 var nw: MappedFile.Node.Writer = undefined;
33163338 const si = isi.symbol(elf);
......@@ -3707,10 +3729,16 @@ pub fn deleteExport(elf: *Elf, exported: Zcu.Exported, name: InternPool.NullTerm
37073729 _ = name;
37083730}
37093731
3710pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) void {
3711 const w, _ = std.debug.lockStderrWriter(&.{});
3712 defer std.debug.unlockStderrWriter();
3713 elf.printNode(tid, w, .root, 0) catch {};
3732pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) Io.Cancelable!void {
3733 const comp = elf.base.comp;
3734 const io = comp.io;
3735 var buffer: [512]u8 = undefined;
3736 const stderr = try io.lockStderr(&buffer, null);
3737 defer io.lockStderr();
3738 const w = &stderr.file_writer.interface;
3739 elf.printNode(tid, w, .root, 0) catch |err| switch (err) {
3740 error.WriteFailed => return stderr.err.?,
3741 };
37143742}
37153743
37163744pub fn printNode(
......@@ -3822,19 +3850,3 @@ pub fn printNode(
38223850 try w.writeByte('\n');
38233851 }
38243852}
3825
3826const assert = std.debug.assert;
3827const builtin = @import("builtin");
3828const codegen = @import("../codegen.zig");
3829const Compilation = @import("../Compilation.zig");
3830const Elf = @This();
3831const InternPool = @import("../InternPool.zig");
3832const link = @import("../link.zig");
3833const log = std.log.scoped(.link);
3834const MappedFile = @import("MappedFile.zig");
3835const native_endian = builtin.cpu.arch.endian();
3836const std = @import("std");
3837const target_util = @import("../target.zig");
3838const Type = @import("../Type.zig");
3839const Value = @import("../Value.zig");
3840const Zcu = @import("../Zcu.zig");
src/link/Lld.zig+34-30
......@@ -359,6 +359,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void {
359359fn coffLink(lld: *Lld, arena: Allocator) !void {
360360 const comp = lld.base.comp;
361361 const gpa = comp.gpa;
362 const io = comp.io;
362363 const base = &lld.base;
363364 const coff = &lld.ofmt.coff;
364365
......@@ -400,11 +401,12 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
400401 // regarding eliding redundant object -> object transformations.
401402 return error.NoObjectsToLink;
402403 };
403 try std.fs.Dir.copyFile(
404 try Io.Dir.copyFile(
404405 the_object_path.root_dir.handle,
405406 the_object_path.sub_path,
406407 directory.handle,
407408 base.emit.sub_path,
409 io,
408410 .{},
409411 );
410412 } else {
......@@ -718,13 +720,13 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
718720 argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena));
719721 continue;
720722 }
721 if (try findLib(arena, lib_basename, coff.lib_directories)) |full_path| {
723 if (try findLib(arena, io, lib_basename, coff.lib_directories)) |full_path| {
722724 argv.appendAssumeCapacity(full_path);
723725 continue;
724726 }
725727 if (target.abi.isGnu()) {
726728 const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key});
727 if (try findLib(arena, fallback_name, coff.lib_directories)) |full_path| {
729 if (try findLib(arena, io, fallback_name, coff.lib_directories)) |full_path| {
728730 argv.appendAssumeCapacity(full_path);
729731 continue;
730732 }
......@@ -741,9 +743,9 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
741743 try spawnLld(comp, arena, argv.items);
742744 }
743745}
744fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Cache.Directory) !?[]const u8 {
746fn findLib(arena: Allocator, io: Io, name: []const u8, lib_directories: []const Cache.Directory) !?[]const u8 {
745747 for (lib_directories) |lib_directory| {
746 lib_directory.handle.access(name, .{}) catch |err| switch (err) {
748 lib_directory.handle.access(io, name, .{}) catch |err| switch (err) {
747749 error.FileNotFound => continue,
748750 else => |e| return e,
749751 };
......@@ -755,6 +757,7 @@ fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Cache.Di
755757fn elfLink(lld: *Lld, arena: Allocator) !void {
756758 const comp = lld.base.comp;
757759 const gpa = comp.gpa;
760 const io = comp.io;
758761 const diags = &comp.link_diags;
759762 const base = &lld.base;
760763 const elf = &lld.ofmt.elf;
......@@ -816,11 +819,12 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
816819 // regarding eliding redundant object -> object transformations.
817820 return error.NoObjectsToLink;
818821 };
819 try std.fs.Dir.copyFile(
822 try Io.Dir.copyFile(
820823 the_object_path.root_dir.handle,
821824 the_object_path.sub_path,
822825 directory.handle,
823826 base.emit.sub_path,
827 io,
824828 .{},
825829 );
826830 } else {
......@@ -1326,6 +1330,7 @@ fn getLDMOption(target: *const std.Target) ?[]const u8 {
13261330}
13271331fn wasmLink(lld: *Lld, arena: Allocator) !void {
13281332 const comp = lld.base.comp;
1333 const diags = &comp.link_diags;
13291334 const shared_memory = comp.config.shared_memory;
13301335 const export_memory = comp.config.export_memory;
13311336 const import_memory = comp.config.import_memory;
......@@ -1334,6 +1339,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
13341339 const wasm = &lld.ofmt.wasm;
13351340
13361341 const gpa = comp.gpa;
1342 const io = comp.io;
13371343
13381344 const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
13391345 const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
......@@ -1371,11 +1377,12 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
13711377 // regarding eliding redundant object -> object transformations.
13721378 return error.NoObjectsToLink;
13731379 };
1374 try fs.Dir.copyFile(
1380 try Io.Dir.copyFile(
13751381 the_object_path.root_dir.handle,
13761382 the_object_path.sub_path,
13771383 directory.handle,
13781384 base.emit.sub_path,
1385 io,
13791386 .{},
13801387 );
13811388 } else {
......@@ -1565,27 +1572,23 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
15651572 // is not the case, it means we will get "exec format error" when trying to run
15661573 // it, and then can react to that in the same way as trying to run an ELF file
15671574 // from a foreign CPU architecture.
1568 if (fs.has_executable_bit and target.os.tag == .wasi and
1575 if (Io.File.Permissions.has_executable_bit and target.os.tag == .wasi and
15691576 comp.config.output_mode == .Exe)
15701577 {
1571 // TODO: what's our strategy for reporting linker errors from this function?
1572 // report a nice error here with the file path if it fails instead of
1573 // just returning the error code.
15741578 // chmod does not interact with umask, so we use a conservative -rwxr--r-- here.
1575 std.posix.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) {
1576 error.OperationNotSupported => unreachable, // Not a symlink.
1577 else => |e| return e,
1578 };
1579 Io.Dir.cwd().setFilePermissions(io, full_out_path, .fromMode(0o744), .{}) catch |err|
1580 return diags.fail("{s}: failed to enable executable permissions: {t}", .{ full_out_path, err });
15791581 }
15801582 }
15811583}
15821584
15831585fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !void {
15841586 const io = comp.io;
1587 const gpa = comp.gpa;
15851588
15861589 if (comp.verbose_link) {
15871590 // Skip over our own name so that the LLD linker name is the first argv item.
1588 Compilation.dump_argv(argv[1..]);
1591 try Compilation.dumpArgv(io, argv[1..]);
15891592 }
15901593
15911594 // If possible, we run LLD as a child process because it does not always
......@@ -1599,7 +1602,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
15991602 }
16001603
16011604 var stderr: []u8 = &.{};
1602 defer comp.gpa.free(stderr);
1605 defer gpa.free(stderr);
16031606
16041607 var child = std.process.Child.init(argv, arena);
16051608 const term = (if (comp.clang_passthrough_mode) term: {
......@@ -1607,16 +1610,16 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
16071610 child.stdout_behavior = .Inherit;
16081611 child.stderr_behavior = .Inherit;
16091612
1610 break :term child.spawnAndWait();
1613 break :term child.spawnAndWait(io);
16111614 } else term: {
16121615 child.stdin_behavior = .Ignore;
16131616 child.stdout_behavior = .Ignore;
16141617 child.stderr_behavior = .Pipe;
16151618
1616 child.spawn() catch |err| break :term err;
1619 child.spawn(io) catch |err| break :term err;
16171620 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
1618 stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited);
1619 break :term child.wait();
1621 stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited);
1622 break :term child.wait(io);
16201623 }) catch |first_err| term: {
16211624 const err = switch (first_err) {
16221625 error.NameTooLong => err: {
......@@ -1624,13 +1627,13 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
16241627 const rand_int = std.crypto.random.int(u64);
16251628 const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp";
16261629
1627 const rsp_file = try comp.dirs.local_cache.handle.createFile(rsp_path, .{});
1628 defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err|
1629 log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) });
1630 const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{});
1631 defer comp.dirs.local_cache.handle.deleteFile(io, rsp_path) catch |err|
1632 log.warn("failed to delete response file {s}: {t}", .{ rsp_path, err });
16301633 {
1631 defer rsp_file.close();
1634 defer rsp_file.close(io);
16321635 var rsp_file_buffer: [1024]u8 = undefined;
1633 var rsp_file_writer = rsp_file.writer(&rsp_file_buffer);
1636 var rsp_file_writer = rsp_file.writer(io, &rsp_file_buffer);
16341637 const rsp_writer = &rsp_file_writer.interface;
16351638 for (argv[2..]) |arg| {
16361639 try rsp_writer.writeByte('"');
......@@ -1657,16 +1660,16 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
16571660 rsp_child.stdout_behavior = .Inherit;
16581661 rsp_child.stderr_behavior = .Inherit;
16591662
1660 break :term rsp_child.spawnAndWait() catch |err| break :err err;
1663 break :term rsp_child.spawnAndWait(io) catch |err| break :err err;
16611664 } else {
16621665 rsp_child.stdin_behavior = .Ignore;
16631666 rsp_child.stdout_behavior = .Ignore;
16641667 rsp_child.stderr_behavior = .Pipe;
16651668
1666 rsp_child.spawn() catch |err| break :err err;
1669 rsp_child.spawn(io) catch |err| break :err err;
16671670 var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{});
1668 stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited);
1669 break :term rsp_child.wait() catch |err| break :err err;
1671 stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited);
1672 break :term rsp_child.wait(io) catch |err| break :err err;
16701673 }
16711674 },
16721675 else => first_err,
......@@ -1692,6 +1695,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
16921695}
16931696
16941697const std = @import("std");
1698const Io = std.Io;
16951699const Allocator = std.mem.Allocator;
16961700const Cache = std.Build.Cache;
16971701const allocPrint = std.fmt.allocPrint;
src/link/MachO.zig+146-89
......@@ -219,10 +219,12 @@ pub fn createEmpty(
219219 };
220220 errdefer self.base.destroy();
221221
222 self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
222 const io = comp.io;
223
224 self.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{
223225 .truncate = true,
224226 .read = true,
225 .mode = link.File.determineMode(output_mode, link_mode),
227 .permissions = link.File.determinePermissions(output_mode, link_mode),
226228 });
227229
228230 // Append null file
......@@ -267,14 +269,16 @@ pub fn open(
267269}
268270
269271pub fn deinit(self: *MachO) void {
270 const gpa = self.base.comp.gpa;
272 const comp = self.base.comp;
273 const gpa = comp.gpa;
274 const io = comp.io;
271275
272276 if (self.d_sym) |*d_sym| {
273277 d_sym.deinit();
274278 }
275279
276280 for (self.file_handles.items) |handle| {
277 handle.close();
281 handle.close(io);
278282 }
279283 self.file_handles.deinit(gpa);
280284
......@@ -343,7 +347,8 @@ pub fn flush(
343347
344348 const comp = self.base.comp;
345349 const gpa = comp.gpa;
346 const diags = &self.base.comp.link_diags;
350 const io = comp.io;
351 const diags = &comp.link_diags;
347352
348353 const sub_prog_node = prog_node.start("MachO Flush", 0);
349354 defer sub_prog_node.end();
......@@ -376,26 +381,26 @@ pub fn flush(
376381 // in this set.
377382 try positionals.ensureUnusedCapacity(comp.c_object_table.keys().len);
378383 for (comp.c_object_table.keys()) |key| {
379 positionals.appendAssumeCapacity(try link.openObjectInput(diags, key.status.success.object_path));
384 positionals.appendAssumeCapacity(try link.openObjectInput(io, diags, key.status.success.object_path));
380385 }
381386
382 if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
387 if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path));
383388
384389 if (comp.config.any_sanitize_thread) {
385 try positionals.append(try link.openObjectInput(diags, comp.tsan_lib.?.full_object_path));
390 try positionals.append(try link.openObjectInput(io, diags, comp.tsan_lib.?.full_object_path));
386391 }
387392
388393 if (comp.config.any_fuzz) {
389 try positionals.append(try link.openArchiveInput(diags, comp.fuzzer_lib.?.full_object_path, false, false));
394 try positionals.append(try link.openArchiveInput(io, diags, comp.fuzzer_lib.?.full_object_path, false, false));
390395 }
391396
392397 if (comp.ubsan_rt_lib) |crt_file| {
393398 const path = crt_file.full_object_path;
394 self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err|
399 self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err|
395400 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
396401 } else if (comp.ubsan_rt_obj) |crt_file| {
397402 const path = crt_file.full_object_path;
398 self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err|
403 self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err|
399404 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
400405 }
401406
......@@ -430,7 +435,7 @@ pub fn flush(
430435 if (comp.config.link_libc and is_exe_or_dyn_lib) {
431436 if (comp.zigc_static_lib) |zigc| {
432437 const path = zigc.full_object_path;
433 self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err|
438 self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err|
434439 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
435440 }
436441 }
......@@ -453,12 +458,12 @@ pub fn flush(
453458 for (system_libs.items) |lib| {
454459 switch (Compilation.classifyFileExt(lib.path.sub_path)) {
455460 .shared_library => {
456 const dso_input = try link.openDsoInput(diags, lib.path, lib.needed, lib.weak, lib.reexport);
461 const dso_input = try link.openDsoInput(io, diags, lib.path, lib.needed, lib.weak, lib.reexport);
457462 self.classifyInputFile(dso_input) catch |err|
458463 diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});
459464 },
460465 .static_library => {
461 const archive_input = try link.openArchiveInput(diags, lib.path, lib.must_link, lib.hidden);
466 const archive_input = try link.openArchiveInput(io, diags, lib.path, lib.must_link, lib.hidden);
462467 self.classifyInputFile(archive_input) catch |err|
463468 diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});
464469 },
......@@ -469,11 +474,11 @@ pub fn flush(
469474 // Finally, link against compiler_rt.
470475 if (comp.compiler_rt_lib) |crt_file| {
471476 const path = crt_file.full_object_path;
472 self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err|
477 self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err|
473478 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
474479 } else if (comp.compiler_rt_obj) |crt_file| {
475480 const path = crt_file.full_object_path;
476 self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err|
481 self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err|
477482 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
478483 }
479484
......@@ -564,7 +569,7 @@ pub fn flush(
564569 self.writeLinkeditSectionsToFile() catch |err| switch (err) {
565570 error.OutOfMemory => return error.OutOfMemory,
566571 error.LinkFailure => return error.LinkFailure,
567 else => |e| return diags.fail("failed to write linkedit sections to file: {s}", .{@errorName(e)}),
572 else => |e| return diags.fail("failed to write linkedit sections to file: {t}", .{e}),
568573 };
569574
570575 var codesig: ?CodeSignature = if (self.requiresCodeSig()) blk: {
......@@ -575,8 +580,8 @@ pub fn flush(
575580 // where the code signature goes into.
576581 var codesig = CodeSignature.init(self.getPageSize());
577582 codesig.code_directory.ident = fs.path.basename(self.base.emit.sub_path);
578 if (self.entitlements) |path| codesig.addEntitlements(gpa, path) catch |err|
579 return diags.fail("failed to add entitlements from {s}: {s}", .{ path, @errorName(err) });
583 if (self.entitlements) |path| codesig.addEntitlements(gpa, io, path) catch |err|
584 return diags.fail("failed to add entitlements from {s}: {t}", .{ path, err });
580585 try self.writeCodeSignaturePadding(&codesig);
581586 break :blk codesig;
582587 } else null;
......@@ -612,15 +617,17 @@ pub fn flush(
612617 else => |e| return diags.fail("failed to write code signature: {s}", .{@errorName(e)}),
613618 };
614619 const emit = self.base.emit;
615 invalidateKernelCache(emit.root_dir.handle, emit.sub_path) catch |err| switch (err) {
616 else => |e| return diags.fail("failed to invalidate kernel cache: {s}", .{@errorName(e)}),
620 invalidateKernelCache(io, emit.root_dir.handle, emit.sub_path) catch |err| switch (err) {
621 else => |e| return diags.fail("failed to invalidate kernel cache: {t}", .{e}),
617622 };
618623 }
619624}
620625
621626/// --verbose-link output
622627fn dumpArgv(self: *MachO, comp: *Compilation) !void {
623 const gpa = self.base.comp.gpa;
628 const gpa = comp.gpa;
629 const io = comp.io;
630
624631 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
625632 defer arena_allocator.deinit();
626633 const arena = arena_allocator.allocator();
......@@ -815,7 +822,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
815822 if (comp.ubsan_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena));
816823 }
817824
818 Compilation.dump_argv(argv.items);
825 try Compilation.dumpArgv(io, argv.items);
819826}
820827
821828/// TODO delete this, libsystem must be resolved when setting up the compilation pipeline
......@@ -825,7 +832,8 @@ pub fn resolveLibSystem(
825832 comp: *Compilation,
826833 out_libs: anytype,
827834) !void {
828 const diags = &self.base.comp.link_diags;
835 const io = comp.io;
836 const diags = &comp.link_diags;
829837
830838 var test_path = std.array_list.Managed(u8).init(arena);
831839 var checked_paths = std.array_list.Managed([]const u8).init(arena);
......@@ -834,16 +842,16 @@ pub fn resolveLibSystem(
834842 if (self.sdk_layout) |sdk_layout| switch (sdk_layout) {
835843 .sdk => {
836844 const dir = try fs.path.join(arena, &.{ comp.sysroot.?, "usr", "lib" });
837 if (try accessLibPath(arena, &test_path, &checked_paths, dir, "System")) break :success;
845 if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success;
838846 },
839847 .vendored => {
840848 const dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "darwin" });
841 if (try accessLibPath(arena, &test_path, &checked_paths, dir, "System")) break :success;
849 if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success;
842850 },
843851 };
844852
845853 for (self.lib_directories) |directory| {
846 if (try accessLibPath(arena, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success;
854 if (try accessLibPath(arena, io, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success;
847855 }
848856
849857 diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{});
......@@ -861,6 +869,9 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
861869 const tracy = trace(@src());
862870 defer tracy.end();
863871
872 const comp = self.base.comp;
873 const io = comp.io;
874
864875 const path, const file = input.pathAndFile().?;
865876 // TODO don't classify now, it's too late. The input file has already been classified
866877 log.debug("classifying input file {f}", .{path});
......@@ -871,7 +882,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
871882 const fat_arch: ?fat.Arch = try self.parseFatFile(file, path);
872883 const offset = if (fat_arch) |fa| fa.offset else 0;
873884
874 if (readMachHeader(file, offset) catch null) |h| blk: {
885 if (readMachHeader(io, file, offset) catch null) |h| blk: {
875886 if (h.magic != macho.MH_MAGIC_64) break :blk;
876887 switch (h.filetype) {
877888 macho.MH_OBJECT => try self.addObject(path, fh, offset),
......@@ -880,7 +891,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
880891 }
881892 return;
882893 }
883 if (readArMagic(file, offset, &buffer) catch null) |ar_magic| blk: {
894 if (readArMagic(io, file, offset, &buffer) catch null) |ar_magic| blk: {
884895 if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk;
885896 try self.addArchive(input.archive, fh, fat_arch);
886897 return;
......@@ -888,12 +899,14 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
888899 _ = try self.addTbd(.fromLinkInput(input), true, fh);
889900}
890901
891fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch {
892 const diags = &self.base.comp.link_diags;
893 const fat_h = fat.readFatHeader(file) catch return null;
902fn parseFatFile(self: *MachO, file: Io.File, path: Path) !?fat.Arch {
903 const comp = self.base.comp;
904 const io = comp.io;
905 const diags = &comp.link_diags;
906 const fat_h = fat.readFatHeader(io, file) catch return null;
894907 if (fat_h.magic != macho.FAT_MAGIC and fat_h.magic != macho.FAT_MAGIC_64) return null;
895908 var fat_archs_buffer: [2]fat.Arch = undefined;
896 const fat_archs = try fat.parseArchs(file, fat_h, &fat_archs_buffer);
909 const fat_archs = try fat.parseArchs(io, file, fat_h, &fat_archs_buffer);
897910 const cpu_arch = self.getTarget().cpu.arch;
898911 for (fat_archs) |arch| {
899912 if (arch.tag == cpu_arch) return arch;
......@@ -901,16 +914,16 @@ fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch {
901914 return diags.failParse(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)});
902915}
903916
904pub fn readMachHeader(file: std.fs.File, offset: usize) !macho.mach_header_64 {
917pub fn readMachHeader(io: Io, file: Io.File, offset: usize) !macho.mach_header_64 {
905918 var buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
906 const nread = try file.preadAll(&buffer, offset);
919 const nread = try file.readPositionalAll(io, &buffer, offset);
907920 if (nread != buffer.len) return error.InputOutput;
908921 const hdr = @as(*align(1) const macho.mach_header_64, @ptrCast(&buffer)).*;
909922 return hdr;
910923}
911924
912pub fn readArMagic(file: std.fs.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 {
913 const nread = try file.preadAll(buffer, offset);
925pub fn readArMagic(io: Io, file: Io.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 {
926 const nread = try file.readPositionalAll(io, buffer, offset);
914927 if (nread != buffer.len) return error.InputOutput;
915928 return buffer[0..Archive.SARMAG];
916929}
......@@ -921,6 +934,7 @@ fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u
921934
922935 const comp = self.base.comp;
923936 const gpa = comp.gpa;
937 const io = comp.io;
924938
925939 const abs_path = try std.fs.path.resolvePosix(gpa, &.{
926940 comp.dirs.cwd,
......@@ -930,7 +944,7 @@ fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u
930944 errdefer gpa.free(abs_path);
931945
932946 const file = self.getFileHandle(handle_index);
933 const stat = try file.stat();
947 const stat = try file.stat(io);
934948 const mtime = stat.mtime.toSeconds();
935949 const index: File.Index = @intCast(try self.files.addOne(gpa));
936950 self.files.set(index, .{ .object = .{
......@@ -1069,6 +1083,7 @@ fn isHoisted(self: *MachO, install_name: []const u8) bool {
10691083/// TODO delete this, libraries must be instead resolved when instantiating the compilation pipeline
10701084fn accessLibPath(
10711085 arena: Allocator,
1086 io: Io,
10721087 test_path: *std.array_list.Managed(u8),
10731088 checked_paths: *std.array_list.Managed([]const u8),
10741089 search_dir: []const u8,
......@@ -1080,7 +1095,7 @@ fn accessLibPath(
10801095 test_path.clearRetainingCapacity();
10811096 try test_path.print("{s}" ++ sep ++ "lib{s}{s}", .{ search_dir, name, ext });
10821097 try checked_paths.append(try arena.dupe(u8, test_path.items));
1083 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
1098 Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) {
10841099 error.FileNotFound => continue,
10851100 else => |e| return e,
10861101 };
......@@ -1092,6 +1107,7 @@ fn accessLibPath(
10921107
10931108fn accessFrameworkPath(
10941109 arena: Allocator,
1110 io: Io,
10951111 test_path: *std.array_list.Managed(u8),
10961112 checked_paths: *std.array_list.Managed([]const u8),
10971113 search_dir: []const u8,
......@@ -1108,7 +1124,7 @@ fn accessFrameworkPath(
11081124 ext,
11091125 });
11101126 try checked_paths.append(try arena.dupe(u8, test_path.items));
1111 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
1127 Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) {
11121128 error.FileNotFound => continue,
11131129 else => |e| return e,
11141130 };
......@@ -1124,7 +1140,9 @@ fn parseDependentDylibs(self: *MachO) !void {
11241140
11251141 if (self.dylibs.items.len == 0) return;
11261142
1127 const gpa = self.base.comp.gpa;
1143 const comp = self.base.comp;
1144 const gpa = comp.gpa;
1145 const io = comp.io;
11281146 const framework_dirs = self.framework_dirs;
11291147
11301148 // TODO delete this, directories must instead be resolved by the frontend
......@@ -1165,14 +1183,14 @@ fn parseDependentDylibs(self: *MachO) !void {
11651183 // Framework
11661184 for (framework_dirs) |dir| {
11671185 test_path.clearRetainingCapacity();
1168 if (try accessFrameworkPath(arena, &test_path, &checked_paths, dir, stem)) break :full_path test_path.items;
1186 if (try accessFrameworkPath(arena, io, &test_path, &checked_paths, dir, stem)) break :full_path test_path.items;
11691187 }
11701188
11711189 // Library
11721190 const lib_name = eatPrefix(stem, "lib") orelse stem;
11731191 for (lib_directories) |lib_directory| {
11741192 test_path.clearRetainingCapacity();
1175 if (try accessLibPath(arena, &test_path, &checked_paths, lib_directory.path orelse ".", lib_name)) break :full_path test_path.items;
1193 if (try accessLibPath(arena, io, &test_path, &checked_paths, lib_directory.path orelse ".", lib_name)) break :full_path test_path.items;
11761194 }
11771195 }
11781196
......@@ -1181,13 +1199,13 @@ fn parseDependentDylibs(self: *MachO) !void {
11811199 const path = if (existing_ext.len > 0) id.name[0 .. id.name.len - existing_ext.len] else id.name;
11821200 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
11831201 test_path.clearRetainingCapacity();
1184 if (self.base.comp.sysroot) |root| {
1202 if (comp.sysroot) |root| {
11851203 try test_path.print("{s}" ++ fs.path.sep_str ++ "{s}{s}", .{ root, path, ext });
11861204 } else {
11871205 try test_path.print("{s}{s}", .{ path, ext });
11881206 }
11891207 try checked_paths.append(try arena.dupe(u8, test_path.items));
1190 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
1208 Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) {
11911209 error.FileNotFound => continue,
11921210 else => |e| return e,
11931211 };
......@@ -1202,7 +1220,8 @@ fn parseDependentDylibs(self: *MachO) !void {
12021220 const rel_path = try fs.path.join(arena, &.{ prefix, path });
12031221 try checked_paths.append(rel_path);
12041222 var buffer: [fs.max_path_bytes]u8 = undefined;
1205 const full_path = fs.realpath(rel_path, &buffer) catch continue;
1223 // TODO don't use realpath
1224 const full_path = buffer[0 .. Io.Dir.realPathFileAbsolute(io, rel_path, &buffer) catch continue];
12061225 break :full_path try arena.dupe(u8, full_path);
12071226 }
12081227 } else if (eatPrefix(id.name, "@loader_path/")) |_| {
......@@ -1215,8 +1234,9 @@ fn parseDependentDylibs(self: *MachO) !void {
12151234
12161235 try checked_paths.append(try arena.dupe(u8, id.name));
12171236 var buffer: [fs.max_path_bytes]u8 = undefined;
1218 if (fs.realpath(id.name, &buffer)) |full_path| {
1219 break :full_path try arena.dupe(u8, full_path);
1237 // TODO don't use realpath
1238 if (Io.Dir.realPathFileAbsolute(io, id.name, &buffer)) |full_path_n| {
1239 break :full_path try arena.dupe(u8, buffer[0..full_path_n]);
12201240 } else |_| {
12211241 try self.reportMissingDependencyError(
12221242 self.getFile(dylib_index).?.dylib.getUmbrella(self).index,
......@@ -1233,12 +1253,12 @@ fn parseDependentDylibs(self: *MachO) !void {
12331253 .path = Path.initCwd(full_path),
12341254 .weak = is_weak,
12351255 };
1236 const file = try lib.path.root_dir.handle.openFile(lib.path.sub_path, .{});
1256 const file = try lib.path.root_dir.handle.openFile(io, lib.path.sub_path, .{});
12371257 const fh = try self.addFileHandle(file);
12381258 const fat_arch = try self.parseFatFile(file, lib.path);
12391259 const offset = if (fat_arch) |fa| fa.offset else 0;
12401260 const file_index = file_index: {
1241 if (readMachHeader(file, offset) catch null) |h| blk: {
1261 if (readMachHeader(io, file, offset) catch null) |h| blk: {
12421262 if (h.magic != macho.MH_MAGIC_64) break :blk;
12431263 switch (h.filetype) {
12441264 macho.MH_DYLIB => break :file_index try self.addDylib(lib, false, fh, offset),
......@@ -3147,7 +3167,9 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) !?u64 {
31473167 }
31483168 }
31493169
3150 if (at_end) try self.base.file.?.setEndPos(end);
3170 const comp = self.base.comp;
3171 const io = comp.io;
3172 if (at_end) try self.base.file.?.setLength(io, end);
31513173 return null;
31523174}
31533175
......@@ -3232,21 +3254,36 @@ pub fn findFreeSpaceVirtual(self: *MachO, object_size: u64, min_alignment: u32)
32323254}
32333255
32343256pub fn copyRangeAll(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {
3235 const file = self.base.file.?;
3236 const amt = try file.copyRangeAll(old_offset, file, new_offset, size);
3237 if (amt != size) return error.InputOutput;
3257 return self.base.copyRangeAll(old_offset, new_offset, size);
32383258}
32393259
3240/// Like File.copyRangeAll but also ensures the source region is zeroed out after copy.
3260/// Like copyRangeAll but also ensures the source region is zeroed out after copy.
32413261/// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader.
32423262fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {
3243 const gpa = self.base.comp.gpa;
3244 try self.copyRangeAll(old_offset, new_offset, size);
3263 const comp = self.base.comp;
3264 const io = comp.io;
3265 const file = self.base.file.?;
3266 var write_buffer: [2048]u8 = undefined;
3267 var file_reader = file.reader(io, &.{});
3268 file_reader.pos = old_offset;
3269 var file_writer = file.writer(io, &write_buffer);
3270 file_writer.pos = new_offset;
32453271 const size_u = math.cast(usize, size) orelse return error.Overflow;
3246 const zeroes = try gpa.alloc(u8, size_u); // TODO no need to allocate here.
3247 defer gpa.free(zeroes);
3248 @memset(zeroes, 0);
3249 try self.base.file.?.pwriteAll(zeroes, old_offset);
3272 const n = file_writer.interface.sendFileAll(&file_reader, .limited(size_u)) catch |err| switch (err) {
3273 error.ReadFailed => return file_reader.err.?,
3274 error.WriteFailed => return file_writer.err.?,
3275 };
3276 assert(n == size_u);
3277 file_writer.seekTo(old_offset) catch |err| switch (err) {
3278 error.WriteFailed => return file_writer.err.?,
3279 else => |e| return e,
3280 };
3281 file_writer.interface.splatByteAll(0, size_u) catch |err| switch (err) {
3282 error.WriteFailed => return file_writer.err.?,
3283 };
3284 file_writer.interface.flush() catch |err| switch (err) {
3285 error.WriteFailed => return file_writer.err.?,
3286 };
32503287}
32513288
32523289const InitMetadataOptions = struct {
......@@ -3257,8 +3294,10 @@ const InitMetadataOptions = struct {
32573294};
32583295
32593296pub fn closeDebugInfo(self: *MachO) bool {
3297 const comp = self.base.comp;
3298 const io = comp.io;
32603299 const d_sym = &(self.d_sym orelse return false);
3261 d_sym.file.?.close();
3300 d_sym.file.?.close(io);
32623301 d_sym.file = null;
32633302 return true;
32643303}
......@@ -3269,7 +3308,9 @@ pub fn reopenDebugInfo(self: *MachO) !void {
32693308 assert(!self.base.comp.config.use_llvm);
32703309 assert(self.base.comp.config.debug_format == .dwarf);
32713310
3272 const gpa = self.base.comp.gpa;
3311 const comp = self.base.comp;
3312 const io = comp.io;
3313 const gpa = comp.gpa;
32733314 const sep = fs.path.sep_str;
32743315 const d_sym_path = try std.fmt.allocPrint(
32753316 gpa,
......@@ -3278,10 +3319,10 @@ pub fn reopenDebugInfo(self: *MachO) !void {
32783319 );
32793320 defer gpa.free(d_sym_path);
32803321
3281 var d_sym_bundle = try self.base.emit.root_dir.handle.makeOpenPath(d_sym_path, .{});
3282 defer d_sym_bundle.close();
3322 var d_sym_bundle = try self.base.emit.root_dir.handle.createDirPathOpen(io, d_sym_path, .{});
3323 defer d_sym_bundle.close(io);
32833324
3284 self.d_sym.?.file = try d_sym_bundle.createFile(fs.path.basename(self.base.emit.sub_path), .{
3325 self.d_sym.?.file = try d_sym_bundle.createFile(io, fs.path.basename(self.base.emit.sub_path), .{
32853326 .truncate = false,
32863327 .read = true,
32873328 });
......@@ -3289,6 +3330,10 @@ pub fn reopenDebugInfo(self: *MachO) !void {
32893330
32903331// TODO: move to ZigObject
32913332fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
3333 const comp = self.base.comp;
3334 const gpa = comp.gpa;
3335 const io = comp.io;
3336
32923337 if (!self.base.isRelocatable()) {
32933338 const base_vmaddr = blk: {
32943339 const pagezero_size = self.pagezero_size orelse default_pagezero_size;
......@@ -3343,7 +3388,11 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
33433388 if (options.zo.dwarf) |*dwarf| {
33443389 // Create dSYM bundle.
33453390 log.debug("creating {s}.dSYM bundle", .{options.emit.sub_path});
3346 self.d_sym = .{ .allocator = self.base.comp.gpa, .file = null };
3391 self.d_sym = .{
3392 .io = io,
3393 .allocator = gpa,
3394 .file = null,
3395 };
33473396 try self.reopenDebugInfo();
33483397 try self.d_sym.?.initMetadata(self);
33493398 try dwarf.initMetadata();
......@@ -3463,6 +3512,9 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo
34633512 const seg_id = self.sections.items(.segment_id)[sect_index];
34643513 const seg = &self.segments.items[seg_id];
34653514
3515 const comp = self.base.comp;
3516 const io = comp.io;
3517
34663518 if (!sect.isZerofill()) {
34673519 const allocated_size = self.allocatedSize(sect.offset);
34683520 if (needed_size > allocated_size) {
......@@ -3484,7 +3536,7 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo
34843536
34853537 sect.offset = @intCast(new_offset);
34863538 } else if (sect.offset + allocated_size == std.math.maxInt(u64)) {
3487 try self.base.file.?.setEndPos(sect.offset + needed_size);
3539 try self.base.file.?.setLength(io, sect.offset + needed_size);
34883540 }
34893541 seg.filesize = needed_size;
34903542 }
......@@ -3506,6 +3558,8 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo
35063558}
35073559
35083560fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void {
3561 const comp = self.base.comp;
3562 const io = comp.io;
35093563 const sect = &self.sections.items(.header)[sect_index];
35103564
35113565 if (!sect.isZerofill()) {
......@@ -3533,7 +3587,7 @@ fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void
35333587 sect.offset = @intCast(new_offset);
35343588 sect.addr = new_addr;
35353589 } else if (sect.offset + allocated_size == std.math.maxInt(u64)) {
3536 try self.base.file.?.setEndPos(sect.offset + needed_size);
3590 try self.base.file.?.setLength(io, sect.offset + needed_size);
35373591 }
35383592 }
35393593 sect.size = needed_size;
......@@ -3567,11 +3621,11 @@ pub fn getTarget(self: *const MachO) *const std.Target {
35673621/// into a new inode, remove the original file, and rename the copy to match
35683622/// the original file. This is super messy, but there doesn't seem any other
35693623/// way to please the XNU.
3570pub fn invalidateKernelCache(dir: fs.Dir, sub_path: []const u8) !void {
3624pub fn invalidateKernelCache(io: Io, dir: Io.Dir, sub_path: []const u8) !void {
35713625 const tracy = trace(@src());
35723626 defer tracy.end();
35733627 if (builtin.target.os.tag.isDarwin() and builtin.target.cpu.arch == .aarch64) {
3574 try dir.copyFile(sub_path, dir, sub_path, .{});
3628 try dir.copyFile(sub_path, dir, sub_path, io, .{});
35753629 }
35763630}
35773631
......@@ -3762,7 +3816,7 @@ pub fn getInternalObject(self: *MachO) ?*InternalObject {
37623816 return self.getFile(index).?.internal;
37633817}
37643818
3765pub fn addFileHandle(self: *MachO, file: fs.File) !File.HandleIndex {
3819pub fn addFileHandle(self: *MachO, file: Io.File) !File.HandleIndex {
37663820 const gpa = self.base.comp.gpa;
37673821 const index: File.HandleIndex = @intCast(self.file_handles.items.len);
37683822 const fh = try self.file_handles.addOne(gpa);
......@@ -4333,11 +4387,13 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi
43334387 defer arena_allocator.deinit();
43344388 const arena = arena_allocator.allocator();
43354389
4390 const io = comp.io;
4391
43364392 const sdk_dir = switch (sdk_layout) {
43374393 .sdk => comp.sysroot.?,
43384394 .vendored => fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "libc", "darwin" }) catch return null,
43394395 };
4340 if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| {
4396 if (readSdkVersionFromSettings(arena, io, sdk_dir)) |ver| {
43414397 return parseSdkVersion(ver);
43424398 } else |_| {
43434399 // Read from settings should always succeed when vendored.
......@@ -4360,9 +4416,9 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi
43604416// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
43614417// Use property `MinimalDisplayName` to determine version.
43624418// The file/property is also available with vendored libc.
4363fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 {
4419fn readSdkVersionFromSettings(arena: Allocator, io: Io, dir: []const u8) ![]const u8 {
43644420 const sdk_path = try fs.path.join(arena, &.{ dir, "SDKSettings.json" });
4365 const contents = try fs.cwd().readFileAlloc(sdk_path, arena, .limited(std.math.maxInt(u16)));
4421 const contents = try Io.Dir.cwd().readFileAlloc(io, sdk_path, arena, .limited(std.math.maxInt(u16)));
43664422 const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
43674423 if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
43684424 return error.SdkVersionFailure;
......@@ -5324,18 +5380,18 @@ fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool {
53245380
53255381pub fn pwriteAll(macho_file: *MachO, bytes: []const u8, offset: u64) error{LinkFailure}!void {
53265382 const comp = macho_file.base.comp;
5383 const io = comp.io;
53275384 const diags = &comp.link_diags;
5328 macho_file.base.file.?.pwriteAll(bytes, offset) catch |err| {
5329 return diags.fail("failed to write: {s}", .{@errorName(err)});
5330 };
5385 macho_file.base.file.?.writePositionalAll(io, bytes, offset) catch |err|
5386 return diags.fail("failed to write: {t}", .{err});
53315387}
53325388
5333pub fn setEndPos(macho_file: *MachO, length: u64) error{LinkFailure}!void {
5389pub fn setLength(macho_file: *MachO, length: u64) error{LinkFailure}!void {
53345390 const comp = macho_file.base.comp;
5391 const io = comp.io;
53355392 const diags = &comp.link_diags;
5336 macho_file.base.file.?.setEndPos(length) catch |err| {
5337 return diags.fail("failed to set file end pos: {s}", .{@errorName(err)});
5338 };
5393 macho_file.base.file.?.setLength(io, length) catch |err|
5394 return diags.fail("failed to set file end pos: {t}", .{err});
53395395}
53405396
53415397pub fn cast(macho_file: *MachO, comptime T: type, x: anytype) error{LinkFailure}!T {
......@@ -5367,10 +5423,11 @@ const max_distance = (1 << (jump_bits - 1));
53675423const max_allowed_distance = max_distance - 0x500_000;
53685424
53695425const MachO = @This();
5370
5371const std = @import("std");
53725426const build_options = @import("build_options");
53735427const builtin = @import("builtin");
5428
5429const std = @import("std");
5430const Io = std.Io;
53745431const assert = std.debug.assert;
53755432const fs = std.fs;
53765433const log = std.log.scoped(.link);
......@@ -5380,6 +5437,11 @@ const math = std.math;
53805437const mem = std.mem;
53815438const meta = std.meta;
53825439const Writer = std.Io.Writer;
5440const AtomicBool = std.atomic.Value(bool);
5441const Cache = std.Build.Cache;
5442const Hash = std.hash.Wyhash;
5443const Md5 = std.crypto.hash.Md5;
5444const Allocator = std.mem.Allocator;
53835445
53845446const aarch64 = codegen.aarch64.encoding;
53855447const bind = @import("MachO/dyld_info/bind.zig");
......@@ -5397,11 +5459,8 @@ const trace = @import("../tracy.zig").trace;
53975459const synthetic = @import("MachO/synthetic.zig");
53985460
53995461const Alignment = Atom.Alignment;
5400const Allocator = mem.Allocator;
54015462const Archive = @import("MachO/Archive.zig");
5402const AtomicBool = std.atomic.Value(bool);
54035463const Bind = bind.Bind;
5404const Cache = std.Build.Cache;
54055464const CodeSignature = @import("MachO/CodeSignature.zig");
54065465const Compilation = @import("../Compilation.zig");
54075466const DataInCode = synthetic.DataInCode;
......@@ -5411,14 +5470,12 @@ const ExportTrie = @import("MachO/dyld_info/Trie.zig");
54115470const Path = Cache.Path;
54125471const File = @import("MachO/file.zig").File;
54135472const GotSection = synthetic.GotSection;
5414const Hash = std.hash.Wyhash;
54155473const Indsymtab = synthetic.Indsymtab;
54165474const InternalObject = @import("MachO/InternalObject.zig");
54175475const ObjcStubsSection = synthetic.ObjcStubsSection;
54185476const Object = @import("MachO/Object.zig");
54195477const LazyBind = bind.LazyBind;
54205478const LaSymbolPtrSection = synthetic.LaSymbolPtrSection;
5421const Md5 = std.crypto.hash.Md5;
54225479const Zcu = @import("../Zcu.zig");
54235480const InternPool = @import("../InternPool.zig");
54245481const Rebase = @import("MachO/dyld_info/Rebase.zig");
src/link/MachO/Archive.zig+4-3
......@@ -6,6 +6,7 @@ pub fn deinit(self: *Archive, allocator: Allocator) void {
66
77pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void {
88 const comp = macho_file.base.comp;
9 const io = comp.io;
910 const gpa = comp.gpa;
1011 const diags = &comp.link_diags;
1112
......@@ -14,7 +15,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File
1415
1516 const handle = macho_file.getFileHandle(handle_index);
1617 const offset = if (fat_arch) |ar| ar.offset else 0;
17 const end_pos = if (fat_arch) |ar| offset + ar.size else (try handle.stat()).size;
18 const end_pos = if (fat_arch) |ar| offset + ar.size else (try handle.stat(io)).size;
1819
1920 var pos: usize = offset + SARMAG;
2021 while (true) {
......@@ -23,7 +24,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File
2324
2425 var hdr_buffer: [@sizeOf(ar_hdr)]u8 = undefined;
2526 {
26 const amt = try handle.preadAll(&hdr_buffer, pos);
27 const amt = try handle.readPositionalAll(io, &hdr_buffer, pos);
2728 if (amt != @sizeOf(ar_hdr)) return error.InputOutput;
2829 }
2930 const hdr = @as(*align(1) const ar_hdr, @ptrCast(&hdr_buffer)).*;
......@@ -41,7 +42,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File
4142 if (try hdr.nameLength()) |len| {
4243 hdr_size -= len;
4344 const buf = try arena.allocator().alloc(u8, len);
44 const amt = try handle.preadAll(buf, pos);
45 const amt = try handle.readPositionalAll(io, buf, pos);
4546 if (amt != len) return error.InputOutput;
4647 pos += len;
4748 const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len;
src/link/MachO/CodeSignature.zig+22-19
......@@ -1,20 +1,28 @@
11const CodeSignature = @This();
22
33const std = @import("std");
4const Io = std.Io;
45const assert = std.debug.assert;
56const fs = std.fs;
67const log = std.log.scoped(.link);
78const macho = std.macho;
89const mem = std.mem;
910const testing = std.testing;
11const Sha256 = std.crypto.hash.sha2.Sha256;
12const Allocator = std.mem.Allocator;
13
1014const trace = @import("../../tracy.zig").trace;
11const Allocator = mem.Allocator;
12const Hasher = @import("hasher.zig").ParallelHasher;
15const ParallelHasher = @import("hasher.zig").ParallelHasher;
1316const MachO = @import("../MachO.zig");
14const Sha256 = std.crypto.hash.sha2.Sha256;
1517
1618const hash_size = Sha256.digest_length;
1719
20page_size: u16,
21code_directory: CodeDirectory,
22requirements: ?Requirements = null,
23entitlements: ?Entitlements = null,
24signature: ?Signature = null,
25
1826const Blob = union(enum) {
1927 code_directory: *CodeDirectory,
2028 requirements: *Requirements,
......@@ -218,12 +226,6 @@ const Signature = struct {
218226 }
219227};
220228
221page_size: u16,
222code_directory: CodeDirectory,
223requirements: ?Requirements = null,
224entitlements: ?Entitlements = null,
225signature: ?Signature = null,
226
227229pub fn init(page_size: u16) CodeSignature {
228230 return .{
229231 .page_size = page_size,
......@@ -244,13 +246,13 @@ pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
244246 }
245247}
246248
247pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
248 const inner = try fs.cwd().readFileAlloc(path, allocator, .limited(std.math.maxInt(u32)));
249pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, io: Io, path: []const u8) !void {
250 const inner = try Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(std.math.maxInt(u32)));
249251 self.entitlements = .{ .inner = inner };
250252}
251253
252254pub const WriteOpts = struct {
253 file: fs.File,
255 file: Io.File,
254256 exec_seg_base: u64,
255257 exec_seg_limit: u64,
256258 file_size: u32,
......@@ -266,7 +268,9 @@ pub fn writeAdhocSignature(
266268 const tracy = trace(@src());
267269 defer tracy.end();
268270
269 const allocator = macho_file.base.comp.gpa;
271 const comp = macho_file.base.comp;
272 const gpa = comp.gpa;
273 const io = comp.io;
270274
271275 var header: macho.SuperBlob = .{
272276 .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
......@@ -274,7 +278,7 @@ pub fn writeAdhocSignature(
274278 .count = 0,
275279 };
276280
277 var blobs = std.array_list.Managed(Blob).init(allocator);
281 var blobs = std.array_list.Managed(Blob).init(gpa);
278282 defer blobs.deinit();
279283
280284 self.code_directory.inner.execSegBase = opts.exec_seg_base;
......@@ -284,13 +288,12 @@ pub fn writeAdhocSignature(
284288
285289 const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
286290
287 try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages);
291 try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages);
288292 self.code_directory.code_slots.items.len = total_pages;
289293 self.code_directory.inner.nCodeSlots = total_pages;
290294
291295 // Calculate hash for each page (in file) and write it to the buffer
292 var hasher = Hasher(Sha256){ .allocator = allocator, .io = macho_file.base.comp.io };
293 try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
296 try ParallelHasher(Sha256).hash(gpa, io, opts.file, self.code_directory.code_slots.items, .{
294297 .chunk_size = self.page_size,
295298 .max_file_size = opts.file_size,
296299 });
......@@ -302,7 +305,7 @@ pub fn writeAdhocSignature(
302305 var hash: [hash_size]u8 = undefined;
303306
304307 if (self.requirements) |*req| {
305 var a: std.Io.Writer.Allocating = .init(allocator);
308 var a: std.Io.Writer.Allocating = .init(gpa);
306309 defer a.deinit();
307310 try req.write(&a.writer);
308311 Sha256.hash(a.written(), &hash, .{});
......@@ -314,7 +317,7 @@ pub fn writeAdhocSignature(
314317 }
315318
316319 if (self.entitlements) |*ents| {
317 var a: std.Io.Writer.Allocating = .init(allocator);
320 var a: std.Io.Writer.Allocating = .init(gpa);
318321 defer a.deinit();
319322 try ents.write(&a.writer);
320323 Sha256.hash(a.written(), &hash, .{});
src/link/MachO/DebugSymbols.zig+45-46
......@@ -1,5 +1,28 @@
1const DebugSymbols = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const assert = std.debug.assert;
6const fs = std.fs;
7const log = std.log.scoped(.link_dsym);
8const macho = std.macho;
9const makeStaticString = MachO.makeStaticString;
10const math = std.math;
11const mem = std.mem;
12const Writer = std.Io.Writer;
13const Allocator = std.mem.Allocator;
14
15const link = @import("../../link.zig");
16const MachO = @import("../MachO.zig");
17const StringTable = @import("../StringTable.zig");
18const Type = @import("../../Type.zig");
19const trace = @import("../../tracy.zig").trace;
20const load_commands = @import("load_commands.zig");
21const padToIdeal = MachO.padToIdeal;
22
23io: Io,
124allocator: Allocator,
2file: ?fs.File,
25file: ?Io.File,
326
427symtab_cmd: macho.symtab_command = .{},
528uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 },
......@@ -102,6 +125,7 @@ pub fn growSection(
102125 requires_file_copy: bool,
103126 macho_file: *MachO,
104127) !void {
128 const io = self.io;
105129 const sect = self.getSectionPtr(sect_index);
106130
107131 const allocated_size = self.allocatedSize(sect.offset);
......@@ -111,25 +135,17 @@ pub fn growSection(
111135 const new_offset = try self.findFreeSpace(needed_size, 1);
112136
113137 log.debug("moving {s} section: {} bytes from 0x{x} to 0x{x}", .{
114 sect.sectName(),
115 existing_size,
116 sect.offset,
117 new_offset,
138 sect.sectName(), existing_size, sect.offset, new_offset,
118139 });
119140
120141 if (requires_file_copy) {
121 const amt = try self.file.?.copyRangeAll(
122 sect.offset,
123 self.file.?,
124 new_offset,
125 existing_size,
126 );
127 if (amt != existing_size) return error.InputOutput;
142 const file = self.file.?;
143 try link.File.copyRangeAll2(io, file, file, sect.offset, new_offset, existing_size);
128144 }
129145
130146 sect.offset = @intCast(new_offset);
131147 } else if (sect.offset + allocated_size == std.math.maxInt(u64)) {
132 try self.file.?.setEndPos(sect.offset + needed_size);
148 try self.file.?.setLength(io, sect.offset + needed_size);
133149 }
134150
135151 sect.size = needed_size;
......@@ -153,6 +169,7 @@ pub fn markDirty(self: *DebugSymbols, sect_index: u8, macho_file: *MachO) void {
153169}
154170
155171fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 {
172 const io = self.io;
156173 var at_end = true;
157174 const end = start + padToIdeal(size);
158175
......@@ -165,7 +182,7 @@ fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 {
165182 }
166183 }
167184
168 if (at_end) try self.file.?.setEndPos(end);
185 if (at_end) try self.file.?.setLength(io, end);
169186 return null;
170187}
171188
......@@ -179,6 +196,7 @@ fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64
179196}
180197
181198pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {
199 const io = self.io;
182200 const zo = macho_file.getZigObject().?;
183201 for (self.relocs.items) |*reloc| {
184202 const sym = zo.symbols.items[reloc.target];
......@@ -190,12 +208,9 @@ pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {
190208 const sect = &self.sections.items[self.debug_info_section_index.?];
191209 const file_offset = sect.offset + reloc.offset;
192210 log.debug("resolving relocation: {d}@{x} ('{s}') at offset {x}", .{
193 reloc.target,
194 addr,
195 sym_name,
196 file_offset,
211 reloc.target, addr, sym_name, file_offset,
197212 });
198 try self.file.?.pwriteAll(mem.asBytes(&addr), file_offset);
213 try self.file.?.writePositionalAll(io, mem.asBytes(&addr), file_offset);
199214 }
200215
201216 self.finalizeDwarfSegment(macho_file);
......@@ -208,7 +223,8 @@ pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {
208223
209224pub fn deinit(self: *DebugSymbols) void {
210225 const gpa = self.allocator;
211 if (self.file) |file| file.close();
226 const io = self.io;
227 if (self.file) |file| file.close(io);
212228 self.segments.deinit(gpa);
213229 self.sections.deinit(gpa);
214230 self.relocs.deinit(gpa);
......@@ -268,6 +284,7 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void {
268284}
269285
270286fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, usize } {
287 const io = self.io;
271288 const gpa = self.allocator;
272289 const needed_size = load_commands.calcLoadCommandsSizeDsym(macho_file, self);
273290 const buffer = try gpa.alloc(u8, needed_size);
......@@ -319,12 +336,13 @@ fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, u
319336
320337 assert(writer.end == needed_size);
321338
322 try self.file.?.pwriteAll(buffer, @sizeOf(macho.mach_header_64));
339 try self.file.?.writePositionalAll(io, buffer, @sizeOf(macho.mach_header_64));
323340
324341 return .{ ncmds, buffer.len };
325342}
326343
327344fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {
345 const io = self.io;
328346 var header: macho.mach_header_64 = .{};
329347 header.filetype = macho.MH_DSYM;
330348
......@@ -345,7 +363,7 @@ fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds
345363
346364 log.debug("writing Mach-O header {}", .{header});
347365
348 try self.file.?.pwriteAll(mem.asBytes(&header), 0);
366 try self.file.?.writePositionalAll(io, mem.asBytes(&header), 0);
349367}
350368
351369fn allocatedSize(self: *DebugSymbols, start: u64) u64 {
......@@ -380,6 +398,8 @@ fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void {
380398pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 {
381399 const tracy = trace(@src());
382400 defer tracy.end();
401
402 const io = self.io;
383403 const gpa = self.allocator;
384404 const cmd = &self.symtab_cmd;
385405 cmd.nsyms = macho_file.symtab_cmd.nsyms;
......@@ -403,15 +423,16 @@ pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 {
403423 internal.writeSymtab(macho_file, self);
404424 }
405425
406 try self.file.?.pwriteAll(@ptrCast(self.symtab.items), cmd.symoff);
426 try self.file.?.writePositionalAll(io, @ptrCast(self.symtab.items), cmd.symoff);
407427
408428 return off + cmd.nsyms * @sizeOf(macho.nlist_64);
409429}
410430
411431pub fn writeStrtab(self: *DebugSymbols, off: u32) !u32 {
432 const io = self.io;
412433 const cmd = &self.symtab_cmd;
413434 cmd.stroff = off;
414 try self.file.?.pwriteAll(self.strtab.items, cmd.stroff);
435 try self.file.?.writePositionalAll(io, self.strtab.items, cmd.stroff);
415436 return off + cmd.strsize;
416437}
417438
......@@ -443,25 +464,3 @@ pub fn getSection(self: DebugSymbols, sect: u8) macho.section_64 {
443464 assert(sect < self.sections.items.len);
444465 return self.sections.items[sect];
445466}
446
447const DebugSymbols = @This();
448
449const std = @import("std");
450const build_options = @import("build_options");
451const assert = std.debug.assert;
452const fs = std.fs;
453const link = @import("../../link.zig");
454const load_commands = @import("load_commands.zig");
455const log = std.log.scoped(.link_dsym);
456const macho = std.macho;
457const makeStaticString = MachO.makeStaticString;
458const math = std.math;
459const mem = std.mem;
460const padToIdeal = MachO.padToIdeal;
461const trace = @import("../../tracy.zig").trace;
462const Writer = std.Io.Writer;
463
464const Allocator = mem.Allocator;
465const MachO = @import("../MachO.zig");
466const StringTable = @import("../StringTable.zig");
467const Type = @import("../../Type.zig");
src/link/MachO/Dylib.zig+12-8
......@@ -57,7 +57,9 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
5757 const tracy = trace(@src());
5858 defer tracy.end();
5959
60 const gpa = macho_file.base.comp.gpa;
60 const comp = macho_file.base.comp;
61 const io = comp.io;
62 const gpa = comp.gpa;
6163 const file = macho_file.getFileHandle(self.file_handle);
6264 const offset = self.offset;
6365
......@@ -65,7 +67,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
6567
6668 var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
6769 {
68 const amt = try file.preadAll(&header_buffer, offset);
70 const amt = try file.readPositionalAll(io, &header_buffer, offset);
6971 if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;
7072 }
7173 const header = @as(*align(1) const macho.mach_header_64, @ptrCast(&header_buffer)).*;
......@@ -86,7 +88,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
8688 const lc_buffer = try gpa.alloc(u8, header.sizeofcmds);
8789 defer gpa.free(lc_buffer);
8890 {
89 const amt = try file.preadAll(lc_buffer, offset + @sizeOf(macho.mach_header_64));
91 const amt = try file.readPositionalAll(io, lc_buffer, offset + @sizeOf(macho.mach_header_64));
9092 if (amt != lc_buffer.len) return error.InputOutput;
9193 }
9294
......@@ -103,7 +105,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
103105 const dyld_cmd = cmd.cast(macho.dyld_info_command).?;
104106 const data = try gpa.alloc(u8, dyld_cmd.export_size);
105107 defer gpa.free(data);
106 const amt = try file.preadAll(data, dyld_cmd.export_off + offset);
108 const amt = try file.readPositionalAll(io, data, dyld_cmd.export_off + offset);
107109 if (amt != data.len) return error.InputOutput;
108110 try self.parseTrie(data, macho_file);
109111 },
......@@ -111,7 +113,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
111113 const ld_cmd = cmd.cast(macho.linkedit_data_command).?;
112114 const data = try gpa.alloc(u8, ld_cmd.datasize);
113115 defer gpa.free(data);
114 const amt = try file.preadAll(data, ld_cmd.dataoff + offset);
116 const amt = try file.readPositionalAll(io, data, ld_cmd.dataoff + offset);
115117 if (amt != data.len) return error.InputOutput;
116118 try self.parseTrie(data, macho_file);
117119 },
......@@ -238,13 +240,15 @@ fn parseTbd(self: *Dylib, macho_file: *MachO) !void {
238240 const tracy = trace(@src());
239241 defer tracy.end();
240242
241 const gpa = macho_file.base.comp.gpa;
243 const comp = macho_file.base.comp;
244 const gpa = comp.gpa;
245 const io = comp.io;
242246
243247 log.debug("parsing dylib from stub: {f}", .{self.path});
244248
245249 const file = macho_file.getFileHandle(self.file_handle);
246 var lib_stub = LibStub.loadFromFile(gpa, file) catch |err| {
247 try macho_file.reportParseError2(self.index, "failed to parse TBD file: {s}", .{@errorName(err)});
250 var lib_stub = LibStub.loadFromFile(gpa, io, file) catch |err| {
251 try macho_file.reportParseError2(self.index, "failed to parse TBD file: {t}", .{err});
248252 return error.MalformedTbd;
249253 };
250254 defer lib_stub.deinit();
src/link/MachO/Object.zig+91-62
......@@ -1,3 +1,30 @@
1const Object = @This();
2
3const trace = @import("../../tracy.zig").trace;
4const Archive = @import("Archive.zig");
5const Atom = @import("Atom.zig");
6const Dwarf = @import("Dwarf.zig");
7const File = @import("file.zig").File;
8const MachO = @import("../MachO.zig");
9const Relocation = @import("Relocation.zig");
10const Symbol = @import("Symbol.zig");
11const UnwindInfo = @import("UnwindInfo.zig");
12
13const std = @import("std");
14const Io = std.Io;
15const Writer = std.Io.Writer;
16const assert = std.debug.assert;
17const log = std.log.scoped(.link);
18const macho = std.macho;
19const LoadCommandIterator = macho.LoadCommandIterator;
20const math = std.math;
21const mem = std.mem;
22const Allocator = std.mem.Allocator;
23
24const eh_frame = @import("eh_frame.zig");
25const Cie = eh_frame.Cie;
26const Fde = eh_frame.Fde;
27
128/// Non-zero for fat object files or archives
229offset: u64,
330/// If `in_archive` is not `null`, this is the basename of the object in the archive. Otherwise,
......@@ -75,7 +102,9 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
75102
76103 log.debug("parsing {f}", .{self.fmtPath()});
77104
78 const gpa = macho_file.base.comp.gpa;
105 const comp = macho_file.base.comp;
106 const io = comp.io;
107 const gpa = comp.gpa;
79108 const handle = macho_file.getFileHandle(self.file_handle);
80109 const cpu_arch = macho_file.getTarget().cpu.arch;
81110
......@@ -84,7 +113,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
84113
85114 var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
86115 {
87 const amt = try handle.preadAll(&header_buffer, self.offset);
116 const amt = try handle.readPositionalAll(io, &header_buffer, self.offset);
88117 if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;
89118 }
90119 self.header = @as(*align(1) const macho.mach_header_64, @ptrCast(&header_buffer)).*;
......@@ -105,7 +134,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
105134 const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds);
106135 defer gpa.free(lc_buffer);
107136 {
108 const amt = try handle.preadAll(lc_buffer, self.offset + @sizeOf(macho.mach_header_64));
137 const amt = try handle.readPositionalAll(io, lc_buffer, self.offset + @sizeOf(macho.mach_header_64));
109138 if (amt != self.header.?.sizeofcmds) return error.InputOutput;
110139 }
111140
......@@ -129,14 +158,14 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
129158 const cmd = lc.cast(macho.symtab_command).?;
130159 try self.strtab.resize(gpa, cmd.strsize);
131160 {
132 const amt = try handle.preadAll(self.strtab.items, cmd.stroff + self.offset);
161 const amt = try handle.readPositionalAll(io, self.strtab.items, cmd.stroff + self.offset);
133162 if (amt != self.strtab.items.len) return error.InputOutput;
134163 }
135164
136165 const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64));
137166 defer gpa.free(symtab_buffer);
138167 {
139 const amt = try handle.preadAll(symtab_buffer, cmd.symoff + self.offset);
168 const amt = try handle.readPositionalAll(io, symtab_buffer, cmd.symoff + self.offset);
140169 if (amt != symtab_buffer.len) return error.InputOutput;
141170 }
142171 const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(symtab_buffer.ptr))[0..cmd.nsyms];
......@@ -154,7 +183,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
154183 const buffer = try gpa.alloc(u8, cmd.datasize);
155184 defer gpa.free(buffer);
156185 {
157 const amt = try handle.preadAll(buffer, self.offset + cmd.dataoff);
186 const amt = try handle.readPositionalAll(io, buffer, self.offset + cmd.dataoff);
158187 if (amt != buffer.len) return error.InputOutput;
159188 }
160189 const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry));
......@@ -440,12 +469,14 @@ fn initCstringLiterals(self: *Object, allocator: Allocator, file: File.Handle, m
440469 const tracy = trace(@src());
441470 defer tracy.end();
442471
472 const comp = macho_file.base.comp;
473 const io = comp.io;
443474 const slice = self.sections.slice();
444475
445476 for (slice.items(.header), 0..) |sect, n_sect| {
446477 if (!isCstringLiteral(sect)) continue;
447478
448 const data = try self.readSectionData(allocator, file, @intCast(n_sect));
479 const data = try self.readSectionData(allocator, io, file, @intCast(n_sect));
449480 defer allocator.free(data);
450481
451482 var count: u32 = 0;
......@@ -628,7 +659,9 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO
628659 const tracy = trace(@src());
629660 defer tracy.end();
630661
631 const gpa = macho_file.base.comp.gpa;
662 const comp = macho_file.base.comp;
663 const io = comp.io;
664 const gpa = comp.gpa;
632665 const file = macho_file.getFileHandle(self.file_handle);
633666
634667 var buffer = std.array_list.Managed(u8).init(gpa);
......@@ -647,7 +680,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO
647680 const slice = self.sections.slice();
648681 for (slice.items(.header), slice.items(.subsections), 0..) |header, subs, n_sect| {
649682 if (isCstringLiteral(header) or isFixedSizeLiteral(header)) {
650 const data = try self.readSectionData(gpa, file, @intCast(n_sect));
683 const data = try self.readSectionData(gpa, io, file, @intCast(n_sect));
651684 defer gpa.free(data);
652685
653686 for (subs.items) |sub| {
......@@ -682,7 +715,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO
682715 buffer.resize(target_size) catch unreachable;
683716 const gop = try sections_data.getOrPut(target.n_sect);
684717 if (!gop.found_existing) {
685 gop.value_ptr.* = try self.readSectionData(gpa, file, @intCast(target.n_sect));
718 gop.value_ptr.* = try self.readSectionData(gpa, io, file, @intCast(target.n_sect));
686719 }
687720 const data = gop.value_ptr.*;
688721 const target_off = try macho_file.cast(usize, target.off);
......@@ -1037,9 +1070,11 @@ fn initEhFrameRecords(self: *Object, allocator: Allocator, sect_id: u8, file: Fi
10371070 const sect = slice.items(.header)[sect_id];
10381071 const relocs = slice.items(.relocs)[sect_id];
10391072
1073 const comp = macho_file.base.comp;
1074 const io = comp.io;
10401075 const size = try macho_file.cast(usize, sect.size);
10411076 try self.eh_frame_data.resize(allocator, size);
1042 const amt = try file.preadAll(self.eh_frame_data.items, sect.offset + self.offset);
1077 const amt = try file.readPositionalAll(io, self.eh_frame_data.items, sect.offset + self.offset);
10431078 if (amt != self.eh_frame_data.items.len) return error.InputOutput;
10441079
10451080 // Check for non-personality relocs in FDEs and apply them
......@@ -1138,8 +1173,10 @@ fn initUnwindRecords(self: *Object, allocator: Allocator, sect_id: u8, file: Fil
11381173 }
11391174 };
11401175
1176 const comp = macho_file.base.comp;
1177 const io = comp.io;
11411178 const header = self.sections.items(.header)[sect_id];
1142 const data = try self.readSectionData(allocator, file, sect_id);
1179 const data = try self.readSectionData(allocator, io, file, sect_id);
11431180 defer allocator.free(data);
11441181
11451182 const nrecs = @divExact(data.len, @sizeOf(macho.compact_unwind_entry));
......@@ -1348,7 +1385,9 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void {
13481385 const tracy = trace(@src());
13491386 defer tracy.end();
13501387
1351 const gpa = macho_file.base.comp.gpa;
1388 const comp = macho_file.base.comp;
1389 const io = comp.io;
1390 const gpa = comp.gpa;
13521391 const file = macho_file.getFileHandle(self.file_handle);
13531392
13541393 var dwarf: Dwarf = .{};
......@@ -1358,18 +1397,18 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void {
13581397 const n_sect: u8 = @intCast(index);
13591398 if (sect.attrs() & macho.S_ATTR_DEBUG == 0) continue;
13601399 if (mem.eql(u8, sect.sectName(), "__debug_info")) {
1361 dwarf.debug_info = try self.readSectionData(gpa, file, n_sect);
1400 dwarf.debug_info = try self.readSectionData(gpa, io, file, n_sect);
13621401 }
13631402 if (mem.eql(u8, sect.sectName(), "__debug_abbrev")) {
1364 dwarf.debug_abbrev = try self.readSectionData(gpa, file, n_sect);
1403 dwarf.debug_abbrev = try self.readSectionData(gpa, io, file, n_sect);
13651404 }
13661405 if (mem.eql(u8, sect.sectName(), "__debug_str")) {
1367 dwarf.debug_str = try self.readSectionData(gpa, file, n_sect);
1406 dwarf.debug_str = try self.readSectionData(gpa, io, file, n_sect);
13681407 }
13691408 // __debug_str_offs[ets] section is a new addition in DWARFv5 and is generally
13701409 // required in order to correctly parse strings.
13711410 if (mem.eql(u8, sect.sectName(), "__debug_str_offs")) {
1372 dwarf.debug_str_offsets = try self.readSectionData(gpa, file, n_sect);
1411 dwarf.debug_str_offsets = try self.readSectionData(gpa, io, file, n_sect);
13731412 }
13741413 }
13751414
......@@ -1611,12 +1650,14 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {
16111650 const tracy = trace(@src());
16121651 defer tracy.end();
16131652
1614 const gpa = macho_file.base.comp.gpa;
1653 const comp = macho_file.base.comp;
1654 const io = comp.io;
1655 const gpa = comp.gpa;
16151656 const handle = macho_file.getFileHandle(self.file_handle);
16161657
16171658 var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
16181659 {
1619 const amt = try handle.preadAll(&header_buffer, self.offset);
1660 const amt = try handle.readPositionalAll(io, &header_buffer, self.offset);
16201661 if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;
16211662 }
16221663 self.header = @as(*align(1) const macho.mach_header_64, @ptrCast(&header_buffer)).*;
......@@ -1637,7 +1678,7 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {
16371678 const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds);
16381679 defer gpa.free(lc_buffer);
16391680 {
1640 const amt = try handle.preadAll(lc_buffer, self.offset + @sizeOf(macho.mach_header_64));
1681 const amt = try handle.readPositionalAll(io, lc_buffer, self.offset + @sizeOf(macho.mach_header_64));
16411682 if (amt != self.header.?.sizeofcmds) return error.InputOutput;
16421683 }
16431684
......@@ -1647,14 +1688,14 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {
16471688 const cmd = lc.cast(macho.symtab_command).?;
16481689 try self.strtab.resize(gpa, cmd.strsize);
16491690 {
1650 const amt = try handle.preadAll(self.strtab.items, cmd.stroff + self.offset);
1691 const amt = try handle.readPositionalAll(io, self.strtab.items, cmd.stroff + self.offset);
16511692 if (amt != self.strtab.items.len) return error.InputOutput;
16521693 }
16531694
16541695 const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64));
16551696 defer gpa.free(symtab_buffer);
16561697 {
1657 const amt = try handle.preadAll(symtab_buffer, cmd.symoff + self.offset);
1698 const amt = try handle.readPositionalAll(io, symtab_buffer, cmd.symoff + self.offset);
16581699 if (amt != symtab_buffer.len) return error.InputOutput;
16591700 }
16601701 const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(symtab_buffer.ptr))[0..cmd.nsyms];
......@@ -1689,13 +1730,15 @@ pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *M
16891730}
16901731
16911732pub fn updateArSize(self: *Object, macho_file: *MachO) !void {
1733 const comp = macho_file.base.comp;
1734 const io = comp.io;
16921735 self.output_ar_state.size = if (self.in_archive) |ar| ar.size else size: {
16931736 const file = macho_file.getFileHandle(self.file_handle);
1694 break :size (try file.stat()).size;
1737 break :size (try file.stat(io)).size;
16951738 };
16961739}
16971740
1698pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: anytype) !void {
1741pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: *Writer) !void {
16991742 // Header
17001743 const size = try macho_file.cast(usize, self.output_ar_state.size);
17011744 const basename = std.fs.path.basename(self.path);
......@@ -1703,10 +1746,12 @@ pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writ
17031746 // Data
17041747 const file = macho_file.getFileHandle(self.file_handle);
17051748 // TODO try using copyRangeAll
1706 const gpa = macho_file.base.comp.gpa;
1749 const comp = macho_file.base.comp;
1750 const io = comp.io;
1751 const gpa = comp.gpa;
17071752 const data = try gpa.alloc(u8, size);
17081753 defer gpa.free(data);
1709 const amt = try file.preadAll(data, self.offset);
1754 const amt = try file.readPositionalAll(io, data, self.offset);
17101755 if (amt != size) return error.InputOutput;
17111756 try writer.writeAll(data);
17121757}
......@@ -1811,7 +1856,9 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void {
18111856 const tracy = trace(@src());
18121857 defer tracy.end();
18131858
1814 const gpa = macho_file.base.comp.gpa;
1859 const comp = macho_file.base.comp;
1860 const io = comp.io;
1861 const gpa = comp.gpa;
18151862 const headers = self.sections.items(.header);
18161863 const sections_data = try gpa.alloc([]const u8, headers.len);
18171864 defer {
......@@ -1827,7 +1874,7 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void {
18271874 if (header.isZerofill()) continue;
18281875 const size = try macho_file.cast(usize, header.size);
18291876 const data = try gpa.alloc(u8, size);
1830 const amt = try file.preadAll(data, header.offset + self.offset);
1877 const amt = try file.readPositionalAll(io, data, header.offset + self.offset);
18311878 if (amt != data.len) return error.InputOutput;
18321879 sections_data[n_sect] = data;
18331880 }
......@@ -1850,7 +1897,9 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void {
18501897 const tracy = trace(@src());
18511898 defer tracy.end();
18521899
1853 const gpa = macho_file.base.comp.gpa;
1900 const comp = macho_file.base.comp;
1901 const io = comp.io;
1902 const gpa = comp.gpa;
18541903 const headers = self.sections.items(.header);
18551904 const sections_data = try gpa.alloc([]const u8, headers.len);
18561905 defer {
......@@ -1866,7 +1915,7 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void {
18661915 if (header.isZerofill()) continue;
18671916 const size = try macho_file.cast(usize, header.size);
18681917 const data = try gpa.alloc(u8, size);
1869 const amt = try file.preadAll(data, header.offset + self.offset);
1918 const amt = try file.readPositionalAll(io, data, header.offset + self.offset);
18701919 if (amt != data.len) return error.InputOutput;
18711920 sections_data[n_sect] = data;
18721921 }
......@@ -2482,11 +2531,11 @@ pub fn getUnwindRecord(self: *Object, index: UnwindInfo.Record.Index) *UnwindInf
24822531}
24832532
24842533/// Caller owns the memory.
2485pub fn readSectionData(self: Object, allocator: Allocator, file: File.Handle, n_sect: u8) ![]u8 {
2534pub fn readSectionData(self: Object, allocator: Allocator, io: Io, file: File.Handle, n_sect: u8) ![]u8 {
24862535 const header = self.sections.items(.header)[n_sect];
24872536 const size = math.cast(usize, header.size) orelse return error.Overflow;
24882537 const data = try allocator.alloc(u8, size);
2489 const amt = try file.preadAll(data, header.offset + self.offset);
2538 const amt = try file.readPositionalAll(io, data, header.offset + self.offset);
24902539 errdefer allocator.free(data);
24912540 if (amt != data.len) return error.InputOutput;
24922541 return data;
......@@ -2710,15 +2759,17 @@ const x86_64 = struct {
27102759 handle: File.Handle,
27112760 macho_file: *MachO,
27122761 ) !void {
2713 const gpa = macho_file.base.comp.gpa;
2762 const comp = macho_file.base.comp;
2763 const io = comp.io;
2764 const gpa = comp.gpa;
27142765
27152766 const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info));
27162767 defer gpa.free(relocs_buffer);
2717 const amt = try handle.preadAll(relocs_buffer, sect.reloff + self.offset);
2768 const amt = try handle.readPositionalAll(io, relocs_buffer, sect.reloff + self.offset);
27182769 if (amt != relocs_buffer.len) return error.InputOutput;
27192770 const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(relocs_buffer.ptr))[0..sect.nreloc];
27202771
2721 const code = try self.readSectionData(gpa, handle, n_sect);
2772 const code = try self.readSectionData(gpa, io, handle, n_sect);
27222773 defer gpa.free(code);
27232774
27242775 try out.ensureTotalCapacityPrecise(gpa, relocs.len);
......@@ -2877,15 +2928,17 @@ const aarch64 = struct {
28772928 handle: File.Handle,
28782929 macho_file: *MachO,
28792930 ) !void {
2880 const gpa = macho_file.base.comp.gpa;
2931 const comp = macho_file.base.comp;
2932 const io = comp.io;
2933 const gpa = comp.gpa;
28812934
28822935 const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info));
28832936 defer gpa.free(relocs_buffer);
2884 const amt = try handle.preadAll(relocs_buffer, sect.reloff + self.offset);
2937 const amt = try handle.readPositionalAll(io, relocs_buffer, sect.reloff + self.offset);
28852938 if (amt != relocs_buffer.len) return error.InputOutput;
28862939 const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(relocs_buffer.ptr))[0..sect.nreloc];
28872940
2888 const code = try self.readSectionData(gpa, handle, n_sect);
2941 const code = try self.readSectionData(gpa, io, handle, n_sect);
28892942 defer gpa.free(code);
28902943
28912944 try out.ensureTotalCapacityPrecise(gpa, relocs.len);
......@@ -3061,27 +3114,3 @@ const aarch64 = struct {
30613114 }
30623115 }
30633116};
3064
3065const std = @import("std");
3066const assert = std.debug.assert;
3067const log = std.log.scoped(.link);
3068const macho = std.macho;
3069const math = std.math;
3070const mem = std.mem;
3071const Allocator = std.mem.Allocator;
3072const Writer = std.Io.Writer;
3073
3074const eh_frame = @import("eh_frame.zig");
3075const trace = @import("../../tracy.zig").trace;
3076const Archive = @import("Archive.zig");
3077const Atom = @import("Atom.zig");
3078const Cie = eh_frame.Cie;
3079const Dwarf = @import("Dwarf.zig");
3080const Fde = eh_frame.Fde;
3081const File = @import("file.zig").File;
3082const LoadCommandIterator = macho.LoadCommandIterator;
3083const MachO = @import("../MachO.zig");
3084const Object = @This();
3085const Relocation = @import("Relocation.zig");
3086const Symbol = @import("Symbol.zig");
3087const UnwindInfo = @import("UnwindInfo.zig");
src/link/MachO/ZigObject.zig+14-7
......@@ -171,6 +171,9 @@ pub fn getAtomData(self: ZigObject, macho_file: *MachO, atom: Atom, buffer: []u8
171171 const isec = atom.getInputSection(macho_file);
172172 assert(!isec.isZerofill());
173173
174 const comp = macho_file.base.comp;
175 const io = comp.io;
176
174177 switch (isec.type()) {
175178 macho.S_THREAD_LOCAL_REGULAR => {
176179 const tlv = self.tlv_initializers.get(atom.atom_index).?;
......@@ -182,7 +185,7 @@ pub fn getAtomData(self: ZigObject, macho_file: *MachO, atom: Atom, buffer: []u8
182185 else => {
183186 const sect = macho_file.sections.items(.header)[atom.out_n_sect];
184187 const file_offset = sect.offset + atom.value;
185 const amt = try macho_file.base.file.?.preadAll(buffer, file_offset);
188 const amt = try macho_file.base.file.?.readPositionalAll(io, buffer, file_offset);
186189 if (amt != buffer.len) return error.InputOutput;
187190 },
188191 }
......@@ -290,12 +293,14 @@ pub fn dedupLiterals(self: *ZigObject, lp: MachO.LiteralPool, macho_file: *MachO
290293/// We need this so that we can write to an archive.
291294/// TODO implement writing ZigObject data directly to a buffer instead.
292295pub fn readFileContents(self: *ZigObject, macho_file: *MachO) !void {
293 const diags = &macho_file.base.comp.link_diags;
296 const comp = macho_file.base.comp;
297 const gpa = comp.gpa;
298 const io = comp.io;
299 const diags = &comp.link_diags;
294300 // Size of the output object file is always the offset + size of the strtab
295301 const size = macho_file.symtab_cmd.stroff + macho_file.symtab_cmd.strsize;
296 const gpa = macho_file.base.comp.gpa;
297302 try self.data.resize(gpa, size);
298 const amt = macho_file.base.file.?.preadAll(self.data.items, 0) catch |err|
303 const amt = macho_file.base.file.?.readPositionalAll(io, self.data.items, 0) catch |err|
299304 return diags.fail("failed to read output file: {s}", .{@errorName(err)});
300305 if (amt != size)
301306 return diags.fail("unexpected EOF reading from output file", .{});
......@@ -945,6 +950,8 @@ fn updateNavCode(
945950) link.File.UpdateNavError!void {
946951 const zcu = pt.zcu;
947952 const gpa = zcu.gpa;
953 const comp = zcu.comp;
954 const io = comp.io;
948955 const ip = &zcu.intern_pool;
949956 const nav = ip.getNav(nav_index);
950957
......@@ -1012,8 +1019,8 @@ fn updateNavCode(
10121019
10131020 if (!sect.isZerofill()) {
10141021 const file_offset = sect.offset + atom.value;
1015 macho_file.base.file.?.pwriteAll(code, file_offset) catch |err|
1016 return macho_file.base.cgFail(nav_index, "failed to write output file: {s}", .{@errorName(err)});
1022 macho_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err|
1023 return macho_file.base.cgFail(nav_index, "failed to write output file: {t}", .{err});
10171024 }
10181025}
10191026
......@@ -1493,7 +1500,7 @@ fn writeTrampoline(tr_sym: Symbol, target: Symbol, macho_file: *MachO) !void {
14931500 .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf),
14941501 else => @panic("TODO implement write trampoline for this CPU arch"),
14951502 };
1496 try macho_file.base.file.?.pwriteAll(out, fileoff);
1503 return macho_file.pwriteAll(out, fileoff);
14971504}
14981505
14991506pub fn getOrCreateMetadataForNav(
src/link/MachO/fat.zig+10-8
......@@ -1,20 +1,22 @@
1const builtin = @import("builtin");
2const native_endian = builtin.target.cpu.arch.endian();
3
14const std = @import("std");
5const Io = std.Io;
26const assert = std.debug.assert;
3const builtin = @import("builtin");
47const log = std.log.scoped(.macho);
58const macho = std.macho;
69const mem = std.mem;
7const native_endian = builtin.target.cpu.arch.endian();
810
911const MachO = @import("../MachO.zig");
1012
11pub fn readFatHeader(file: std.fs.File) !macho.fat_header {
12 return readFatHeaderGeneric(macho.fat_header, file, 0);
13pub fn readFatHeader(io: Io, file: Io.File) !macho.fat_header {
14 return readFatHeaderGeneric(io, macho.fat_header, file, 0);
1315}
1416
15fn readFatHeaderGeneric(comptime Hdr: type, file: std.fs.File, offset: usize) !Hdr {
17fn readFatHeaderGeneric(io: Io, comptime Hdr: type, file: Io.File, offset: usize) !Hdr {
1618 var buffer: [@sizeOf(Hdr)]u8 = undefined;
17 const nread = try file.preadAll(&buffer, offset);
19 const nread = try file.readPositionalAll(io, &buffer, offset);
1820 if (nread != buffer.len) return error.InputOutput;
1921 var hdr = @as(*align(1) const Hdr, @ptrCast(&buffer)).*;
2022 mem.byteSwapAllFields(Hdr, &hdr);
......@@ -27,12 +29,12 @@ pub const Arch = struct {
2729 size: u32,
2830};
2931
30pub fn parseArchs(file: std.fs.File, fat_header: macho.fat_header, out: *[2]Arch) ![]const Arch {
32pub fn parseArchs(io: Io, file: Io.File, fat_header: macho.fat_header, out: *[2]Arch) ![]const Arch {
3133 var count: usize = 0;
3234 var fat_arch_index: u32 = 0;
3335 while (fat_arch_index < fat_header.nfat_arch and count < out.len) : (fat_arch_index += 1) {
3436 const offset = @sizeOf(macho.fat_header) + @sizeOf(macho.fat_arch) * fat_arch_index;
35 const fat_arch = try readFatHeaderGeneric(macho.fat_arch, file, offset);
37 const fat_arch = try readFatHeaderGeneric(io, macho.fat_arch, file, offset);
3638 // If we come across an architecture that we do not know how to handle, that's
3739 // fine because we can keep looking for one that might match.
3840 const arch: std.Target.Cpu.Arch = switch (fat_arch.cputype) {
src/link/MachO/file.zig+2-1
......@@ -355,11 +355,12 @@ pub const File = union(enum) {
355355 dylib: Dylib,
356356 };
357357
358 pub const Handle = std.fs.File;
358 pub const Handle = Io.File;
359359 pub const HandleIndex = Index;
360360};
361361
362362const std = @import("std");
363const Io = std.Io;
363364const assert = std.debug.assert;
364365const log = std.log.scoped(.link);
365366const macho = std.macho;
src/link/MachO/hasher.zig+20-28
......@@ -1,34 +1,36 @@
1const std = @import("std");
2const Io = std.Io;
3const assert = std.debug.assert;
4const Allocator = std.mem.Allocator;
5
6const trace = @import("../../tracy.zig").trace;
7
18pub fn ParallelHasher(comptime Hasher: type) type {
29 const hash_size = Hasher.digest_length;
310
411 return struct {
5 allocator: Allocator,
6 io: std.Io,
7
8 pub fn hash(self: Self, file: fs.File, out: [][hash_size]u8, opts: struct {
12 pub fn hash(gpa: Allocator, io: Io, file: Io.File, out: [][hash_size]u8, opts: struct {
913 chunk_size: u64 = 0x4000,
1014 max_file_size: ?u64 = null,
1115 }) !void {
1216 const tracy = trace(@src());
1317 defer tracy.end();
1418
15 const io = self.io;
16
1719 const file_size = blk: {
18 const file_size = opts.max_file_size orelse try file.getEndPos();
20 const file_size = opts.max_file_size orelse try file.length(io);
1921 break :blk std.math.cast(usize, file_size) orelse return error.Overflow;
2022 };
2123 const chunk_size = std.math.cast(usize, opts.chunk_size) orelse return error.Overflow;
2224
23 const buffer = try self.allocator.alloc(u8, chunk_size * out.len);
24 defer self.allocator.free(buffer);
25 const buffer = try gpa.alloc(u8, chunk_size * out.len);
26 defer gpa.free(buffer);
2527
26 const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len);
27 defer self.allocator.free(results);
28 const results = try gpa.alloc(Io.File.ReadPositionalError!usize, out.len);
29 defer gpa.free(results);
2830
2931 {
30 var group: std.Io.Group = .init;
31 errdefer group.cancel(io);
32 var group: Io.Group = .init;
33 defer group.cancel(io);
3234
3335 for (out, results, 0..) |*out_buf, *result, i| {
3436 const fstart = i * chunk_size;
......@@ -37,6 +39,7 @@ pub fn ParallelHasher(comptime Hasher: type) type {
3739 else
3840 chunk_size;
3941 group.async(io, worker, .{
42 io,
4043 file,
4144 fstart,
4245 buffer[fstart..][0..fsize],
......@@ -51,26 +54,15 @@ pub fn ParallelHasher(comptime Hasher: type) type {
5154 }
5255
5356 fn worker(
54 file: fs.File,
57 io: Io,
58 file: Io.File,
5559 fstart: usize,
5660 buffer: []u8,
5761 out: *[hash_size]u8,
58 err: *fs.File.PReadError!usize,
62 err: *Io.File.ReadPositionalError!usize,
5963 ) void {
60 const tracy = trace(@src());
61 defer tracy.end();
62 err.* = file.preadAll(buffer, fstart);
64 err.* = file.readPositionalAll(io, buffer, fstart);
6365 Hasher.hash(buffer, out, .{});
6466 }
65
66 const Self = @This();
6767 };
6868}
69
70const assert = std.debug.assert;
71const fs = std.fs;
72const mem = std.mem;
73const std = @import("std");
74const trace = @import("../../tracy.zig").trace;
75
76const Allocator = mem.Allocator;
src/link/MachO/relocatable.zig+15-15
......@@ -1,6 +1,7 @@
11pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void {
2 const gpa = macho_file.base.comp.gpa;
3 const diags = &macho_file.base.comp.link_diags;
2 const gpa = comp.gpa;
3 const io = comp.io;
4 const diags = &comp.link_diags;
45
56 // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list.
67 var positionals = std.array_list.Managed(link.Input).init(gpa);
......@@ -9,24 +10,22 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
910 positionals.appendSliceAssumeCapacity(comp.link_inputs);
1011
1112 for (comp.c_object_table.keys()) |key| {
12 try positionals.append(try link.openObjectInput(diags, key.status.success.object_path));
13 try positionals.append(try link.openObjectInput(io, diags, key.status.success.object_path));
1314 }
1415
15 if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
16 if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path));
1617
1718 if (macho_file.getZigObject() == null and positionals.items.len == 1) {
1819 // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all
1920 // debug info segments/sections (this is apparently by design by Apple), we copy
2021 // the *only* input file over.
2122 const path = positionals.items[0].path().?;
22 const in_file = path.root_dir.handle.openFile(path.sub_path, .{}) catch |err|
23 const in_file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err|
2324 return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) });
24 const stat = in_file.stat() catch |err|
25 const stat = in_file.stat(io) catch |err|
2526 return diags.fail("failed to stat {f}: {s}", .{ path, @errorName(err) });
26 const amt = in_file.copyRangeAll(0, macho_file.base.file.?, 0, stat.size) catch |err|
27 return diags.fail("failed to copy range of file {f}: {s}", .{ path, @errorName(err) });
28 if (amt != stat.size)
29 return diags.fail("unexpected short write in copy range of file {f}", .{path});
27 link.File.copyRangeAll2(io, in_file, macho_file.base.file.?, 0, 0, stat.size) catch |err|
28 return diags.fail("failed to copy range of file {f}: {t}", .{ path, err });
3029 return;
3130 }
3231
......@@ -79,6 +78,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
7978
8079pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void {
8180 const gpa = comp.gpa;
81 const io = comp.io;
8282 const diags = &macho_file.base.comp.link_diags;
8383
8484 var positionals = std.array_list.Managed(link.Input).init(gpa);
......@@ -88,17 +88,17 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
8888 positionals.appendSliceAssumeCapacity(comp.link_inputs);
8989
9090 for (comp.c_object_table.keys()) |key| {
91 try positionals.append(try link.openObjectInput(diags, key.status.success.object_path));
91 try positionals.append(try link.openObjectInput(io, diags, key.status.success.object_path));
9292 }
9393
94 if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
94 if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path));
9595
9696 if (comp.compiler_rt_strat == .obj) {
97 try positionals.append(try link.openObjectInput(diags, comp.compiler_rt_obj.?.full_object_path));
97 try positionals.append(try link.openObjectInput(io, diags, comp.compiler_rt_obj.?.full_object_path));
9898 }
9999
100100 if (comp.ubsan_rt_strat == .obj) {
101 try positionals.append(try link.openObjectInput(diags, comp.ubsan_rt_obj.?.full_object_path));
101 try positionals.append(try link.openObjectInput(io, diags, comp.ubsan_rt_obj.?.full_object_path));
102102 }
103103
104104 for (positionals.items) |link_input| {
......@@ -229,7 +229,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
229229
230230 assert(writer.end == total_size);
231231
232 try macho_file.setEndPos(total_size);
232 try macho_file.setLength(total_size);
233233 try macho_file.pwriteAll(writer.buffered(), 0);
234234
235235 if (diags.hasErrors()) return error.LinkFailure;
src/link/MachO/uuid.zig+17-16
......@@ -1,28 +1,38 @@
1const std = @import("std");
2const Io = std.Io;
3const Md5 = std.crypto.hash.Md5;
4
5const trace = @import("../../tracy.zig").trace;
6const Compilation = @import("../../Compilation.zig");
7const ParallelHasher = @import("hasher.zig").ParallelHasher;
8
19/// Calculates Md5 hash of each chunk in parallel and then hashes all Md5 hashes to produce
210/// the final digest.
311/// While this is NOT a correct MD5 hash of the contents, this methodology is used by LLVM/LLD
412/// and we will use it too as it seems accepted by Apple OSes.
513/// TODO LLD also hashes the output filename to disambiguate between same builds with different
614/// output files. Should we also do that?
7pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void {
15pub fn calcUuid(comp: *const Compilation, file: Io.File, file_size: u64, out: *[Md5.digest_length]u8) !void {
816 const tracy = trace(@src());
917 defer tracy.end();
1018
19 const gpa = comp.gpa;
20 const io = comp.io;
21
1122 const chunk_size: usize = 1024 * 1024;
1223 const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow;
1324 const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks;
1425
15 const hashes = try comp.gpa.alloc([Md5.digest_length]u8, actual_num_chunks);
16 defer comp.gpa.free(hashes);
26 const hashes = try gpa.alloc([Md5.digest_length]u8, actual_num_chunks);
27 defer gpa.free(hashes);
1728
18 var hasher = Hasher(Md5){ .allocator = comp.gpa, .io = comp.io };
19 try hasher.hash(file, hashes, .{
29 try ParallelHasher(Md5).hash(gpa, io, file, hashes, .{
2030 .chunk_size = chunk_size,
2131 .max_file_size = file_size,
2232 });
2333
24 const final_buffer = try comp.gpa.alloc(u8, actual_num_chunks * Md5.digest_length);
25 defer comp.gpa.free(final_buffer);
34 const final_buffer = try gpa.alloc(u8, actual_num_chunks * Md5.digest_length);
35 defer gpa.free(final_buffer);
2636
2737 for (hashes, 0..) |hash, i| {
2838 @memcpy(final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash);
......@@ -37,12 +47,3 @@ inline fn conform(out: *[Md5.digest_length]u8) void {
3747 out[6] = (out[6] & 0x0F) | (3 << 4);
3848 out[8] = (out[8] & 0x3F) | 0x80;
3949}
40
41const fs = std.fs;
42const mem = std.mem;
43const std = @import("std");
44const trace = @import("../../tracy.zig").trace;
45
46const Compilation = @import("../../Compilation.zig");
47const Md5 = std.crypto.hash.Md5;
48const Hasher = @import("hasher.zig").ParallelHasher;
src/link/MappedFile.zig+61-26
......@@ -1,3 +1,17 @@
1/// TODO add a mapped file abstraction to std.Io
2const MappedFile = @This();
3
4const builtin = @import("builtin");
5const is_linux = builtin.os.tag == .linux;
6const is_windows = builtin.os.tag == .windows;
7
8const std = @import("std");
9const Io = std.Io;
10const assert = std.debug.assert;
11const linux = std.os.linux;
12const windows = std.os.windows;
13
14io: Io,
115file: std.Io.File,
216flags: packed struct {
317 block_size: std.mem.Alignment,
......@@ -16,16 +30,22 @@ writers: std.SinglyLinkedList,
1630
1731pub const growth_factor = 4;
1832
19pub const Error = std.posix.MMapError || std.posix.MRemapError || std.fs.File.SetEndPosError || error{
33pub const Error = std.posix.MMapError || std.posix.MRemapError || Io.File.LengthError || error{
2034 NotFile,
2135 SystemResources,
2236 IsDir,
2337 Unseekable,
2438 NoSpaceLeft,
39
40 InputOutput,
41 FileTooBig,
42 FileBusy,
43 NonResizable,
2544};
2645
27pub fn init(file: std.Io.File, gpa: std.mem.Allocator) !MappedFile {
46pub fn init(file: std.Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile {
2847 var mf: MappedFile = .{
48 .io = io,
2949 .file = file,
3050 .flags = undefined,
3151 .section = if (is_windows) windows.INVALID_HANDLE_VALUE else {},
......@@ -55,18 +75,41 @@ pub fn init(file: std.Io.File, gpa: std.mem.Allocator) !MappedFile {
5575 };
5676 }
5777 if (is_linux) {
58 const statx = try linux.wrapped.statx(
59 mf.file.handle,
60 "",
61 std.posix.AT.EMPTY_PATH,
62 .{ .TYPE = true, .SIZE = true, .BLOCKS = true },
63 );
64 assert(statx.mask.TYPE);
65 assert(statx.mask.SIZE);
66 assert(statx.mask.BLOCKS);
67
68 if (!std.posix.S.ISREG(statx.mode)) return error.PathAlreadyExists;
69 break :stat .{ statx.size, @max(std.heap.pageSize(), statx.blksize) };
78 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
79 .{ .major = 30, .minor = 0, .patch = 0 }
80 else
81 .{ .major = 2, .minor = 28, .patch = 0 });
82 const sys = if (use_c) std.c else std.os.linux;
83 while (true) {
84 var statx = std.mem.zeroes(linux.Statx);
85 const rc = sys.statx(
86 mf.file.handle,
87 "",
88 std.posix.AT.EMPTY_PATH,
89 .{ .TYPE = true, .SIZE = true, .BLOCKS = true },
90 &statx,
91 );
92 switch (sys.errno(rc)) {
93 .SUCCESS => {
94 assert(statx.mask.TYPE);
95 assert(statx.mask.SIZE);
96 assert(statx.mask.BLOCKS);
97 if (!std.posix.S.ISREG(statx.mode)) return error.PathAlreadyExists;
98 break :stat .{ statx.size, @max(std.heap.pageSize(), statx.blksize) };
99 },
100 .INTR => continue,
101 .ACCES => return error.AccessDenied,
102 .BADF => if (std.debug.runtime_safety) unreachable else return error.Unexpected,
103 .FAULT => if (std.debug.runtime_safety) unreachable else return error.Unexpected,
104 .INVAL => if (std.debug.runtime_safety) unreachable else return error.Unexpected,
105 .LOOP => return error.SymLinkLoop,
106 .NAMETOOLONG => return error.NameTooLong,
107 .NOENT => return error.FileNotFound,
108 .NOTDIR => return error.FileNotFound,
109 .NOMEM => return error.SystemResources,
110 else => |err| return std.posix.unexpectedErrno(err),
111 }
112 }
70113 }
71114 const stat = try std.posix.fstat(mf.file.handle);
72115 if (!std.posix.S.ISREG(stat.mode)) return error.PathAlreadyExists;
......@@ -433,8 +476,8 @@ pub const Node = extern struct {
433476 return n;
434477 },
435478 .streaming,
436 .streaming_reading,
437 .positional_reading,
479 .streaming_simple,
480 .positional_simple,
438481 .failure,
439482 => {
440483 const dest = limit.slice(interface.unusedCapacitySlice());
......@@ -612,13 +655,14 @@ pub fn addNodeAfter(
612655}
613656
614657fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested_size: u64) !void {
658 const io = mf.io;
615659 const node = ni.get(mf);
616660 const old_offset, const old_size = node.location().resolve(mf);
617661 const new_size = node.flags.alignment.forward(@intCast(requested_size));
618662 // Resize the entire file
619663 if (ni == Node.Index.root) {
620664 try mf.ensureCapacityForSetLocation(gpa);
621 try std.fs.File.adaptFromNewApi(mf.file).setEndPos(new_size);
665 try mf.file.setLength(io, new_size);
622666 try mf.ensureTotalCapacity(@intCast(new_size));
623667 ni.setLocationAssumeCapacity(mf, old_offset, new_size);
624668 return;
......@@ -1059,12 +1103,3 @@ fn verifyNode(mf: *MappedFile, parent_ni: Node.Index) void {
10591103 ni = node.next;
10601104 }
10611105}
1062
1063const assert = std.debug.assert;
1064const builtin = @import("builtin");
1065const is_linux = builtin.os.tag == .linux;
1066const is_windows = builtin.os.tag == .windows;
1067const linux = std.os.linux;
1068const MappedFile = @This();
1069const std = @import("std");
1070const windows = std.os.windows;
src/link/Queue.zig+3-1
......@@ -121,7 +121,7 @@ pub fn enqueueZcu(
121121 link.doZcuTask(comp, tid, task);
122122}
123123
124pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) void {
124pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) Io.Cancelable!void {
125125 if (q.future != null) {
126126 q.prelink_queue.close(comp.io);
127127 return;
......@@ -136,6 +136,7 @@ pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) void {
136136 } else |err| switch (err) {
137137 error.OutOfMemory => comp.link_diags.setAllocFailure(),
138138 error.LinkFailure => {},
139 error.Canceled => |e| return e,
139140 }
140141 }
141142}
......@@ -175,6 +176,7 @@ fn runLinkTasks(q: *Queue, comp: *Compilation) void {
175176 lf.post_prelink = true;
176177 } else |err| switch (err) {
177178 error.OutOfMemory => comp.link_diags.setAllocFailure(),
179 error.Canceled => @panic("TODO"),
178180 error.LinkFailure => {},
179181 }
180182 }
src/link/SpirV.zig+5-3
......@@ -33,6 +33,7 @@ pub fn createEmpty(
3333 options: link.File.OpenOptions,
3434) !*Linker {
3535 const gpa = comp.gpa;
36 const io = comp.io;
3637 const target = &comp.root_mod.resolved_target.result;
3738
3839 assert(!comp.config.use_lld); // Caught by Compilation.Config.resolve
......@@ -78,7 +79,7 @@ pub fn createEmpty(
7879 };
7980 errdefer linker.deinit();
8081
81 linker.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
82 linker.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{
8283 .truncate = true,
8384 .read = true,
8485 });
......@@ -245,6 +246,7 @@ pub fn flush(
245246 const comp = linker.base.comp;
246247 const diags = &comp.link_diags;
247248 const gpa = comp.gpa;
249 const io = comp.io;
248250
249251 // We need to export the list of error names somewhere so that we can pretty-print them in the
250252 // executor. This is not really an important thing though, so we can just dump it in any old
......@@ -286,8 +288,8 @@ pub fn flush(
286288 };
287289
288290 // TODO endianness bug. use file writer and call writeSliceEndian instead
289 linker.base.file.?.writeAll(@ptrCast(linked_module)) catch |err|
290 return diags.fail("failed to write: {s}", .{@errorName(err)});
291 linker.base.file.?.writeStreamingAll(io, @ptrCast(linked_module)) catch |err|
292 return diags.fail("failed to write: {t}", .{err});
291293}
292294
293295fn linkModule(arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {
src/link/Wasm.zig+38-20
......@@ -20,6 +20,7 @@ const native_endian = builtin.cpu.arch.endian();
2020const build_options = @import("build_options");
2121
2222const std = @import("std");
23const Io = std.Io;
2324const Allocator = std.mem.Allocator;
2425const Cache = std.Build.Cache;
2526const Path = Cache.Path;
......@@ -428,7 +429,11 @@ pub const OutputFunctionIndex = enum(u32) {
428429
429430 pub fn fromSymbolName(wasm: *const Wasm, name: String) OutputFunctionIndex {
430431 if (wasm.flush_buffer.function_imports.getIndex(name)) |i| return @enumFromInt(i);
431 return fromFunctionIndex(wasm, FunctionIndex.fromSymbolName(wasm, name).?);
432 return fromFunctionIndex(wasm, FunctionIndex.fromSymbolName(wasm, name) orelse {
433 if (std.debug.runtime_safety) {
434 std.debug.panic("function index for symbol not found: {s}", .{name.slice(wasm)});
435 } else unreachable;
436 });
432437 }
433438};
434439
......@@ -2996,16 +3001,18 @@ pub fn createEmpty(
29963001 .named => |name| (try wasm.internString(name)).toOptional(),
29973002 };
29983003
2999 wasm.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
3004 const io = comp.io;
3005
3006 wasm.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{
30003007 .truncate = true,
30013008 .read = true,
3002 .mode = if (fs.has_executable_bit)
3009 .permissions = if (Io.File.Permissions.has_executable_bit)
30033010 if (target.os.tag == .wasi and output_mode == .Exe)
3004 fs.File.default_mode | 0b001_000_000
3011 .executable_file
30053012 else
3006 fs.File.default_mode
3013 .default_file
30073014 else
3008 0,
3015 .default_file,
30093016 });
30103017 wasm.name = emit.sub_path;
30113018
......@@ -3013,14 +3020,16 @@ pub fn createEmpty(
30133020}
30143021
30153022fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void {
3016 const diags = &wasm.base.comp.link_diags;
3017 const obj = link.openObject(path, false, false) catch |err| {
3018 switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) {
3023 const comp = wasm.base.comp;
3024 const io = comp.io;
3025 const diags = &comp.link_diags;
3026 const obj = link.openObject(io, path, false, false) catch |err| {
3027 switch (diags.failParse(path, "failed to open object: {t}", .{err})) {
30193028 error.LinkFailure => return,
30203029 }
30213030 };
30223031 wasm.parseObject(obj) catch |err| {
3023 switch (diags.failParse(path, "failed to parse object: {s}", .{@errorName(err)})) {
3032 switch (diags.failParse(path, "failed to parse object: {t}", .{err})) {
30243033 error.LinkFailure => return,
30253034 }
30263035 };
......@@ -3032,7 +3041,7 @@ fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {
30323041 const io = wasm.base.comp.io;
30333042 const gc_sections = wasm.base.gc_sections;
30343043
3035 defer obj.file.close();
3044 defer obj.file.close(io);
30363045
30373046 var file_reader = obj.file.reader(io, &.{});
30383047
......@@ -3060,7 +3069,7 @@ fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {
30603069 const io = wasm.base.comp.io;
30613070 const gc_sections = wasm.base.gc_sections;
30623071
3063 defer obj.file.close();
3072 defer obj.file.close(io);
30643073
30653074 var file_reader = obj.file.reader(io, &.{});
30663075
......@@ -3529,7 +3538,10 @@ pub fn markFunctionImport(
35293538 import: *FunctionImport,
35303539 func_index: FunctionImport.Index,
35313540) link.File.FlushError!void {
3532 if (import.flags.alive) return;
3541 // import.flags.alive might be already true from a previous update. In such
3542 // case, we must still run the logic in this function, in case the item
3543 // being marked was reverted by the `flush` logic that resets the hash
3544 // table watermarks.
35333545 import.flags.alive = true;
35343546
35353547 const comp = wasm.base.comp;
......@@ -3549,8 +3561,9 @@ pub fn markFunctionImport(
35493561 } else {
35503562 try wasm.function_imports.put(gpa, name, .fromObject(func_index, wasm));
35513563 }
3552 } else {
3553 try markFunction(wasm, import.resolution.unpack(wasm).object_function, import.flags.exported);
3564 } else switch (import.resolution.unpack(wasm)) {
3565 .object_function => try markFunction(wasm, import.resolution.unpack(wasm).object_function, import.flags.exported),
3566 else => return,
35543567 }
35553568}
35563569
......@@ -3589,7 +3602,10 @@ fn markGlobalImport(
35893602 import: *GlobalImport,
35903603 global_index: GlobalImport.Index,
35913604) link.File.FlushError!void {
3592 if (import.flags.alive) return;
3605 // import.flags.alive might be already true from a previous update. In such
3606 // case, we must still run the logic in this function, in case the item
3607 // being marked was reverted by the `flush` logic that resets the hash
3608 // table watermarks.
35933609 import.flags.alive = true;
35943610
35953611 const comp = wasm.base.comp;
......@@ -3619,8 +3635,9 @@ fn markGlobalImport(
36193635 } else {
36203636 try wasm.global_imports.put(gpa, name, .fromObject(global_index, wasm));
36213637 }
3622 } else {
3623 try markGlobal(wasm, import.resolution.unpack(wasm).object_global, import.flags.exported);
3638 } else switch (import.resolution.unpack(wasm)) {
3639 .object_global => try markGlobal(wasm, import.resolution.unpack(wasm).object_global, import.flags.exported),
3640 else => return,
36243641 }
36253642}
36263643
......@@ -3823,8 +3840,9 @@ pub fn flush(
38233840 const comp = wasm.base.comp;
38243841 const diags = &comp.link_diags;
38253842 const gpa = comp.gpa;
3843 const io = comp.io;
38263844
3827 if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items);
3845 if (comp.verbose_link) try Compilation.dumpArgv(io, wasm.dump_argv_list.items);
38283846
38293847 if (wasm.base.zcu_object_basename) |raw| {
38303848 const zcu_obj_path: Path = try comp.resolveEmitPathFlush(arena, .temp, raw);
......@@ -4037,7 +4055,7 @@ pub fn tagNameSymbolIndex(wasm: *Wasm, ip_index: InternPool.Index) Allocator.Err
40374055 const comp = wasm.base.comp;
40384056 assert(comp.config.output_mode == .Obj);
40394057 const gpa = comp.gpa;
4040 const name = try wasm.internStringFmt("__zig_tag_name_{d}", .{@intFromEnum(ip_index)});
4058 const name = try wasm.internStringFmt("__zig_tag_name_{d}", .{ip_index});
40414059 const gop = try wasm.symbol_table.getOrPut(gpa, name);
40424060 gop.value_ptr.* = {};
40434061 return @enumFromInt(gop.index);
src/link/Wasm/Flush.zig+9-5
......@@ -108,6 +108,7 @@ pub fn deinit(f: *Flush, gpa: Allocator) void {
108108
109109pub fn finish(f: *Flush, wasm: *Wasm) !void {
110110 const comp = wasm.base.comp;
111 const io = comp.io;
111112 const shared_memory = comp.config.shared_memory;
112113 const diags = &comp.link_diags;
113114 const gpa = comp.gpa;
......@@ -127,17 +128,20 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
127128 if (comp.zcu) |zcu| {
128129 const ip: *const InternPool = &zcu.intern_pool; // No mutations allowed!
129130
131 log.debug("total MIR instructions: {d}", .{wasm.mir_instructions.len});
132
130133 // Detect any intrinsics that were called; they need to have dependencies on the symbols marked.
131134 // Likewise detect `@tagName` calls so those functions can be included in the output and synthesized.
132135 for (wasm.mir_instructions.items(.tag), wasm.mir_instructions.items(.data)) |tag, *data| switch (tag) {
133136 .call_intrinsic => {
134137 const symbol_name = try wasm.internString(@tagName(data.intrinsic));
135138 const i: Wasm.FunctionImport.Index = @enumFromInt(wasm.object_function_imports.getIndex(symbol_name) orelse {
136 return diags.fail("missing compiler runtime intrinsic '{s}' (undefined linker symbol)", .{
137 @tagName(data.intrinsic),
139 return diags.fail("missing compiler runtime intrinsic '{t}' (undefined linker symbol)", .{
140 data.intrinsic,
138141 });
139142 });
140143 try wasm.markFunctionImport(symbol_name, i.value(wasm), i);
144 log.debug("markFunctionImport intrinsic {d}={t}", .{ i, data.intrinsic });
141145 },
142146 .call_tag_name => {
143147 assert(ip.indexToKey(data.ip_index) == .enum_type);
......@@ -146,11 +150,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
146150 wasm.tag_name_table_ref_count += 1;
147151 const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu);
148152 gop.value_ptr.* = .{ .tag_name = .{
149 .symbol_name = try wasm.internStringFmt("__zig_tag_name_{d}", .{@intFromEnum(data.ip_index)}),
153 .symbol_name = try wasm.internStringFmt("__zig_tag_name_{d}", .{data.ip_index}),
150154 .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .slice_const_u8_sentinel_0, target),
151155 .table_index = @intCast(wasm.tag_name_offs.items.len),
152156 } };
153 try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {});
154157 const tag_names = ip.loadEnumType(data.ip_index).names;
155158 for (tag_names.get(ip)) |tag_name| {
156159 const slice = tag_name.toSlice(ip);
......@@ -158,6 +161,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
158161 try wasm.tag_name_bytes.appendSlice(gpa, slice[0 .. slice.len + 1]);
159162 }
160163 }
164 try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {});
161165 },
162166 else => continue,
163167 };
......@@ -1067,7 +1071,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
10671071 }
10681072
10691073 // Finally, write the entire binary into the file.
1070 var file_writer = wasm.base.file.?.writer(&.{});
1074 var file_writer = wasm.base.file.?.writer(io, &.{});
10711075 file_writer.interface.writeAll(binary_bytes.items) catch |err| switch (err) {
10721076 error.WriteFailed => return file_writer.err.?,
10731077 };
src/link/tapi.zig+7-7
......@@ -1,10 +1,10 @@
11const std = @import("std");
2const fs = std.fs;
2const Io = std.Io;
33const mem = std.mem;
44const log = std.log.scoped(.tapi);
5const yaml = @import("tapi/yaml.zig");
5const Allocator = std.mem.Allocator;
66
7const Allocator = mem.Allocator;
7const yaml = @import("tapi/yaml.zig");
88const Yaml = yaml.Yaml;
99
1010const VersionField = union(enum) {
......@@ -130,7 +130,7 @@ pub const Tbd = union(enum) {
130130pub const TapiError = error{
131131 NotLibStub,
132132 InputOutput,
133} || yaml.YamlError || std.fs.File.PReadError;
133} || yaml.YamlError || Io.File.ReadPositionalError;
134134
135135pub const LibStub = struct {
136136 /// Underlying memory for stub's contents.
......@@ -139,14 +139,14 @@ pub const LibStub = struct {
139139 /// Typed contents of the tbd file.
140140 inner: []Tbd,
141141
142 pub fn loadFromFile(allocator: Allocator, file: fs.File) TapiError!LibStub {
142 pub fn loadFromFile(allocator: Allocator, io: Io, file: Io.File) TapiError!LibStub {
143143 const filesize = blk: {
144 const stat = file.stat() catch break :blk std.math.maxInt(u32);
144 const stat = file.stat(io) catch break :blk std.math.maxInt(u32);
145145 break :blk @min(stat.size, std.math.maxInt(u32));
146146 };
147147 const source = try allocator.alloc(u8, filesize);
148148 defer allocator.free(source);
149 const amt = try file.preadAll(source, 0);
149 const amt = try file.readPositionalAll(io, source, 0);
150150 if (amt != filesize) return error.InputOutput;
151151
152152 var lib_stub = LibStub{
src/main.zig+255-244
......@@ -162,17 +162,20 @@ var debug_allocator: std.heap.DebugAllocator(.{
162162 .stack_trace_frames = build_options.mem_leak_frames,
163163}) = .init;
164164
165const use_debug_allocator = build_options.debug_gpa or
166 (native_os != .wasi and !builtin.link_libc and switch (builtin.mode) {
167 .Debug, .ReleaseSafe => true,
168 .ReleaseFast, .ReleaseSmall => false,
169 });
170
165171pub fn main() anyerror!void {
166 const gpa, const is_debug = gpa: {
167 if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true };
168 if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false };
169 if (builtin.link_libc) break :gpa .{ std.heap.c_allocator, false };
170 break :gpa switch (builtin.mode) {
171 .Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true },
172 .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false },
173 };
172 const gpa = gpa: {
173 if (use_debug_allocator) break :gpa debug_allocator.allocator();
174 if (native_os == .wasi) break :gpa std.heap.wasm_allocator;
175 if (builtin.link_libc) break :gpa std.heap.c_allocator;
176 break :gpa std.heap.smp_allocator;
174177 };
175 defer if (is_debug) {
178 defer if (use_debug_allocator) {
176179 _ = debug_allocator.deinit();
177180 };
178181 var arena_instance = std.heap.ArenaAllocator.init(gpa);
......@@ -238,7 +241,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
238241 }
239242 }
240243
241 var threaded: Io.Threaded = .init(gpa);
244 var threaded: Io.Threaded = .init(gpa, .{});
242245 defer threaded.deinit();
243246 threaded_impl_ptr = &threaded;
244247 threaded.stack_size = thread_stack_size;
......@@ -328,23 +331,24 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
328331 .prepend_global_cache_path = true,
329332 });
330333 } else if (mem.eql(u8, cmd, "init")) {
331 return cmdInit(gpa, arena, cmd_args);
334 return cmdInit(gpa, arena, io, cmd_args);
332335 } else if (mem.eql(u8, cmd, "targets")) {
333336 dev.check(.targets_command);
334337 const host = std.zig.resolveTargetQueryOrFatal(io, .{});
335 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
336 try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host);
338 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
339 try @import("print_targets.zig").cmdTargets(arena, io, cmd_args, &stdout_writer.interface, &host);
337340 return stdout_writer.interface.flush();
338341 } else if (mem.eql(u8, cmd, "version")) {
339342 dev.check(.version_command);
340 try fs.File.stdout().writeAll(build_options.version ++ "\n");
343 try Io.File.stdout().writeStreamingAll(io, build_options.version ++ "\n");
341344 return;
342345 } else if (mem.eql(u8, cmd, "env")) {
343346 dev.check(.env_command);
344347 const host = std.zig.resolveTargetQueryOrFatal(io, .{});
345 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
348 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
346349 try @import("print_env.zig").cmdEnv(
347350 arena,
351 io,
348352 &stdout_writer.interface,
349353 args,
350354 if (native_os == .wasi) wasi_preopens,
......@@ -358,10 +362,10 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
358362 });
359363 } else if (mem.eql(u8, cmd, "zen")) {
360364 dev.check(.zen_command);
361 return fs.File.stdout().writeAll(info_zen);
365 return Io.File.stdout().writeStreamingAll(io, info_zen);
362366 } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) {
363367 dev.check(.help_command);
364 return fs.File.stdout().writeAll(usage);
368 return Io.File.stdout().writeStreamingAll(io, usage);
365369 } else if (mem.eql(u8, cmd, "ast-check")) {
366370 return cmdAstCheck(arena, io, cmd_args);
367371 } else if (mem.eql(u8, cmd, "detect-cpu")) {
......@@ -371,7 +375,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
371375 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) {
372376 return cmdDumpZir(arena, io, cmd_args);
373377 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "llvm-ints")) {
374 return cmdDumpLlvmInts(gpa, arena, cmd_args);
378 return cmdDumpLlvmInts(gpa, arena, io, cmd_args);
375379 } else {
376380 std.log.info("{s}", .{usage});
377381 fatal("unknown command: {s}", .{args[1]});
......@@ -698,7 +702,7 @@ const Emit = union(enum) {
698702 yes: []const u8,
699703
700704 const OutputToCacheReason = enum { listen, @"zig run", @"zig test" };
701 fn resolve(emit: Emit, default_basename: []const u8, output_to_cache: ?OutputToCacheReason) Compilation.CreateOptions.Emit {
705 fn resolve(emit: Emit, io: Io, default_basename: []const u8, output_to_cache: ?OutputToCacheReason) Compilation.CreateOptions.Emit {
702706 return switch (emit) {
703707 .no => .no,
704708 .yes_default_path => if (output_to_cache != null) .yes_cache else .{ .yes_path = default_basename },
......@@ -713,10 +717,10 @@ const Emit = union(enum) {
713717 } else e: {
714718 // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would.
715719 if (fs.path.dirname(path)) |dir_path| {
716 var dir = fs.cwd().openDir(dir_path, .{}) catch |err| {
720 var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch |err| {
717721 fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) });
718722 };
719 dir.close();
723 dir.close(io);
720724 }
721725 break :e .{ .yes_path = path };
722726 },
......@@ -1029,13 +1033,12 @@ fn buildOutputType(
10291033 if (mem.cutPrefix(u8, arg, "@")) |resp_file_path| {
10301034 // This is a "compiler response file". We must parse the file and treat its
10311035 // contents as command line parameters.
1032 args_iter.resp_file = initArgIteratorResponseFile(arena, resp_file_path) catch |err| {
1033 fatal("unable to read response file '{s}': {s}", .{ resp_file_path, @errorName(err) });
1034 };
1036 args_iter.resp_file = initArgIteratorResponseFile(arena, io, resp_file_path) catch |err|
1037 fatal("unable to read response file '{s}': {t}", .{ resp_file_path, err });
10351038 } else if (mem.startsWith(u8, arg, "-")) {
10361039 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
1037 try fs.File.stdout().writeAll(usage_build_generic);
1038 return cleanExit();
1040 try Io.File.stdout().writeStreamingAll(io, usage_build_generic);
1041 return cleanExit(io);
10391042 } else if (mem.eql(u8, arg, "--")) {
10401043 if (arg_mode == .run) {
10411044 // args_iter.i is 1, referring the next arg after "--" in ["--", ...]
......@@ -1856,9 +1859,7 @@ fn buildOutputType(
18561859 var must_link = false;
18571860 var file_ext: ?Compilation.FileExt = null;
18581861 while (it.has_next) {
1859 it.next() catch |err| {
1860 fatal("unable to parse command line parameters: {s}", .{@errorName(err)});
1861 };
1862 it.next(io) catch |err| fatal("unable to parse command line parameters: {t}", .{err});
18621863 switch (it.zig_equivalent) {
18631864 .target => target_arch_os_abi = it.only_arg, // example: -target riscv64-linux-unknown
18641865 .o => {
......@@ -2834,9 +2835,9 @@ fn buildOutputType(
28342835 } else if (mem.eql(u8, arg, "-V")) {
28352836 warn("ignoring request for supported emulations: unimplemented", .{});
28362837 } else if (mem.eql(u8, arg, "-v")) {
2837 try fs.File.stdout().writeAll("zig ld " ++ build_options.version ++ "\n");
2838 try Io.File.stdout().writeStreamingAll(io, "zig ld " ++ build_options.version ++ "\n");
28382839 } else if (mem.eql(u8, arg, "--version")) {
2839 try fs.File.stdout().writeAll("zig ld " ++ build_options.version ++ "\n");
2840 try Io.File.stdout().writeStreamingAll(io, "zig ld " ++ build_options.version ++ "\n");
28402841 process.exit(0);
28412842 } else {
28422843 fatal("unsupported linker arg: {s}", .{arg});
......@@ -3075,14 +3076,13 @@ fn buildOutputType(
30753076
30763077 const self_exe_path = switch (native_os) {
30773078 .wasi => {},
3078 else => fs.selfExePathAlloc(arena) catch |err| {
3079 fatal("unable to find zig self exe path: {s}", .{@errorName(err)});
3080 },
3079 else => process.executablePathAlloc(io, arena) catch |err| fatal("unable to find zig self exe path: {t}", .{err}),
30813080 };
30823081
30833082 // This `init` calls `fatal` on error.
30843083 var dirs: Compilation.Directories = .init(
30853084 arena,
3085 io,
30863086 override_lib_dir,
30873087 override_global_cache_dir,
30883088 s: {
......@@ -3095,11 +3095,9 @@ fn buildOutputType(
30953095 if (native_os == .wasi) wasi_preopens,
30963096 self_exe_path,
30973097 );
3098 defer dirs.deinit();
3098 defer dirs.deinit(io);
30993099
3100 if (linker_optimization) |o| {
3101 warn("ignoring deprecated linker optimization setting '{s}'", .{o});
3102 }
3100 if (linker_optimization) |o| warn("ignoring deprecated linker optimization setting '{s}'", .{o});
31033101
31043102 create_module.dirs = dirs;
31053103 create_module.opts.emit_llvm_ir = emit_llvm_ir != .no;
......@@ -3208,6 +3206,7 @@ fn buildOutputType(
32083206
32093207 for (create_module.framework_dirs.items) |framework_dir_path| {
32103208 if (try accessFrameworkPath(
3209 io,
32113210 &test_path,
32123211 &checked_paths,
32133212 framework_dir_path,
......@@ -3251,8 +3250,8 @@ fn buildOutputType(
32513250 }
32523251 }
32533252
3254 var cleanup_emit_bin_dir: ?fs.Dir = null;
3255 defer if (cleanup_emit_bin_dir) |*dir| dir.close();
3253 var cleanup_emit_bin_dir: ?Io.Dir = null;
3254 defer if (cleanup_emit_bin_dir) |*dir| dir.close(io);
32563255
32573256 // For `zig run` and `zig test`, we don't want to put the binary in the cwd by default. So, if
32583257 // the binary is requested with no explicit path (as is the default), we emit to the cache.
......@@ -3304,10 +3303,10 @@ fn buildOutputType(
33043303 } else emit: {
33053304 // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would.
33063305 if (fs.path.dirname(path)) |dir_path| {
3307 var dir = fs.cwd().openDir(dir_path, .{}) catch |err| {
3306 var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch |err| {
33083307 fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) });
33093308 };
3310 dir.close();
3309 dir.close(io);
33113310 }
33123311 break :emit .{ .yes_path = path };
33133312 },
......@@ -3321,18 +3320,18 @@ fn buildOutputType(
33213320 };
33223321
33233322 const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name});
3324 const emit_h_resolved = emit_h.resolve(default_h_basename, output_to_cache);
3323 const emit_h_resolved = emit_h.resolve(io, default_h_basename, output_to_cache);
33253324
33263325 const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name});
3327 const emit_asm_resolved = emit_asm.resolve(default_asm_basename, output_to_cache);
3326 const emit_asm_resolved = emit_asm.resolve(io, default_asm_basename, output_to_cache);
33283327
33293328 const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name});
3330 const emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename, output_to_cache);
3329 const emit_llvm_ir_resolved = emit_llvm_ir.resolve(io, default_llvm_ir_basename, output_to_cache);
33313330
33323331 const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name});
3333 const emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename, output_to_cache);
3332 const emit_llvm_bc_resolved = emit_llvm_bc.resolve(io, default_llvm_bc_basename, output_to_cache);
33343333
3335 const emit_docs_resolved = emit_docs.resolve("docs", output_to_cache);
3334 const emit_docs_resolved = emit_docs.resolve(io, "docs", output_to_cache);
33363335
33373336 const is_exe_or_dyn_lib = switch (create_module.resolved_options.output_mode) {
33383337 .Obj => false,
......@@ -3353,7 +3352,7 @@ fn buildOutputType(
33533352 const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name});
33543353 const emit_implib_resolved: Compilation.CreateOptions.Emit = switch (emit_implib) {
33553354 .no => .no,
3356 .yes => emit_implib.resolve(default_implib_basename, output_to_cache),
3355 .yes => emit_implib.resolve(io, default_implib_basename, output_to_cache),
33573356 .yes_default_path => emit: {
33583357 if (output_to_cache != null) break :emit .yes_cache;
33593358 const p = try fs.path.join(arena, &.{
......@@ -3382,24 +3381,24 @@ fn buildOutputType(
33823381 const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{
33833382 std.crypto.random.int(u64), ext.canonicalName(target),
33843383 });
3385 try dirs.local_cache.handle.makePath("tmp");
3384 try dirs.local_cache.handle.createDirPath(io, "tmp");
33863385
33873386 // Note that in one of the happy paths, execve() is used to switch to
33883387 // clang in which case any cleanup logic that exists for this temporary
33893388 // file will not run and this temp file will be leaked. The filename
33903389 // will be a hash of its contents — so multiple invocations of
33913390 // `zig cc -` will result in the same temp file name.
3392 var f = try dirs.local_cache.handle.createFile(dump_path, .{});
3393 defer f.close();
3391 var f = try dirs.local_cache.handle.createFile(io, dump_path, .{});
3392 defer f.close(io);
33943393
33953394 // Re-using the hasher from Cache, since the functional requirements
33963395 // for the hashing algorithm here and in the cache are the same.
33973396 // We are providing our own cache key, because this file has nothing
33983397 // to do with the cache manifest.
3399 var file_writer = f.writer(&.{});
3398 var file_writer = f.writer(io, &.{});
34003399 var buffer: [1000]u8 = undefined;
34013400 var hasher = file_writer.interface.hashed(Cache.Hasher.init("0123456789abcdef"), &buffer);
3402 var stdin_reader = fs.File.stdin().readerStreaming(io, &.{});
3401 var stdin_reader = Io.File.stdin().readerStreaming(io, &.{});
34033402 _ = hasher.writer.sendFileAll(&stdin_reader, .unlimited) catch |err| switch (err) {
34043403 error.WriteFailed => fatal("failed to write {s}: {t}", .{ dump_path, file_writer.err.? }),
34053404 else => fatal("failed to pipe stdin to {s}: {t}", .{ dump_path, err }),
......@@ -3411,7 +3410,7 @@ fn buildOutputType(
34113410 const sub_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-stdin{s}", .{
34123411 &bin_digest, ext.canonicalName(target),
34133412 });
3414 try dirs.local_cache.handle.rename(dump_path, sub_path);
3413 try dirs.local_cache.handle.rename(dump_path, dirs.local_cache.handle, sub_path, io);
34153414
34163415 // Convert `sub_path` to be relative to current working directory.
34173416 src.src_path = try dirs.local_cache.join(arena, &.{sub_path});
......@@ -3630,13 +3629,13 @@ fn buildOutputType(
36303629 if (show_builtin) {
36313630 const builtin_opts = comp.root_mod.getBuiltinOptions(comp.config);
36323631 const source = try builtin_opts.generate(arena);
3633 return fs.File.stdout().writeAll(source);
3632 return Io.File.stdout().writeStreamingAll(io, source);
36343633 }
36353634 switch (listen) {
36363635 .none => {},
36373636 .stdio => {
3638 var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer);
3639 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
3637 var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer);
3638 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
36403639 try serve(
36413640 comp,
36423641 &stdin_reader.interface,
......@@ -3647,7 +3646,7 @@ fn buildOutputType(
36473646 all_args,
36483647 runtime_args_start,
36493648 );
3650 return cleanExit();
3649 return cleanExit(io);
36513650 },
36523651 .ip4 => |ip4_addr| {
36533652 const addr: Io.net.IpAddress = .{ .ip4 = ip4_addr };
......@@ -3673,12 +3672,12 @@ fn buildOutputType(
36733672 all_args,
36743673 runtime_args_start,
36753674 );
3676 return cleanExit();
3675 return cleanExit(io);
36773676 },
36783677 }
36793678
36803679 {
3681 const root_prog_node = std.Progress.start(.{
3680 const root_prog_node = std.Progress.start(io, .{
36823681 .disable_printing = (color == .off),
36833682 });
36843683 defer root_prog_node.end();
......@@ -3756,7 +3755,7 @@ fn buildOutputType(
37563755 }
37573756
37583757 // Skip resource deallocation in release builds; let the OS do it.
3759 return cleanExit();
3758 return cleanExit(io);
37603759}
37613760
37623761const CreateModule = struct {
......@@ -3927,11 +3926,8 @@ fn createModule(
39273926 }
39283927
39293928 if (target.isMinGW()) {
3930 const exists = mingw.libExists(arena, target, create_module.dirs.zig_lib, lib_name) catch |err| {
3931 fatal("failed to check zig installation for DLL import libs: {s}", .{
3932 @errorName(err),
3933 });
3934 };
3929 const exists = mingw.libExists(arena, io, target, create_module.dirs.zig_lib, lib_name) catch |err|
3930 fatal("failed to check zig installation for DLL import libs: {t}", .{err});
39353931 if (exists) {
39363932 try create_module.windows_libs.put(arena, lib_name, {});
39373933 continue;
......@@ -3959,14 +3955,14 @@ fn createModule(
39593955 if (fs.path.isAbsolute(lib_dir_arg)) {
39603956 const stripped_dir = lib_dir_arg[fs.path.parsePath(lib_dir_arg).root.len..];
39613957 const full_path = try fs.path.join(arena, &[_][]const u8{ root, stripped_dir });
3962 addLibDirectoryWarn(&create_module.lib_directories, full_path);
3958 addLibDirectoryWarn(io, &create_module.lib_directories, full_path);
39633959 } else {
3964 addLibDirectoryWarn(&create_module.lib_directories, lib_dir_arg);
3960 addLibDirectoryWarn(io, &create_module.lib_directories, lib_dir_arg);
39653961 }
39663962 }
39673963 } else {
39683964 for (create_module.lib_dir_args.items) |lib_dir_arg| {
3969 addLibDirectoryWarn(&create_module.lib_directories, lib_dir_arg);
3965 addLibDirectoryWarn(io, &create_module.lib_directories, lib_dir_arg);
39703966 }
39713967 }
39723968 create_module.lib_dir_args = undefined; // From here we use lib_directories instead.
......@@ -3989,9 +3985,8 @@ fn createModule(
39893985 resolved_target.is_native_os and resolved_target.is_native_abi and
39903986 create_module.want_native_include_dirs)
39913987 {
3992 var paths = std.zig.system.NativePaths.detect(arena, target) catch |err| {
3993 fatal("unable to detect native system paths: {s}", .{@errorName(err)});
3994 };
3988 var paths = std.zig.system.NativePaths.detect(arena, io, target) catch |err|
3989 fatal("unable to detect native system paths: {t}", .{err});
39953990 for (paths.warnings.items) |warning| {
39963991 warn("{s}", .{warning});
39973992 }
......@@ -4002,38 +3997,35 @@ fn createModule(
40023997 try create_module.rpath_list.appendSlice(arena, paths.rpaths.items);
40033998
40043999 try create_module.lib_directories.ensureUnusedCapacity(arena, paths.lib_dirs.items.len);
4005 for (paths.lib_dirs.items) |path| addLibDirectoryWarn2(&create_module.lib_directories, path, true);
4000 for (paths.lib_dirs.items) |path| addLibDirectoryWarn2(io, &create_module.lib_directories, path, true);
40064001 }
40074002
40084003 if (create_module.libc_paths_file) |paths_file| {
4009 create_module.libc_installation = LibCInstallation.parse(arena, paths_file, target) catch |err| {
4010 fatal("unable to parse libc paths file at path {s}: {s}", .{
4011 paths_file, @errorName(err),
4012 });
4013 };
4004 create_module.libc_installation = LibCInstallation.parse(arena, io, paths_file, target) catch |err|
4005 fatal("unable to parse libc paths file at path {s}: {t}", .{ paths_file, err });
40144006 }
40154007
40164008 if (target.os.tag == .windows and (target.abi == .msvc or target.abi == .itanium) and
40174009 any_name_queries_remaining)
40184010 {
40194011 if (create_module.libc_installation == null) {
4020 create_module.libc_installation = LibCInstallation.findNative(.{
4021 .allocator = arena,
4012 create_module.libc_installation = LibCInstallation.findNative(arena, io, .{
40224013 .verbose = true,
40234014 .target = target,
40244015 }) catch |err| {
4025 fatal("unable to find native libc installation: {s}", .{@errorName(err)});
4016 fatal("unable to find native libc installation: {t}", .{err});
40264017 };
40274018 }
40284019 try create_module.lib_directories.ensureUnusedCapacity(arena, 2);
4029 addLibDirectoryWarn(&create_module.lib_directories, create_module.libc_installation.?.msvc_lib_dir.?);
4030 addLibDirectoryWarn(&create_module.lib_directories, create_module.libc_installation.?.kernel32_lib_dir.?);
4020 addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.msvc_lib_dir.?);
4021 addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.kernel32_lib_dir.?);
40314022 }
40324023
40334024 // Destructively mutates but does not transfer ownership of `unresolved_link_inputs`.
40344025 link.resolveInputs(
40354026 gpa,
40364027 arena,
4028 io,
40374029 target,
40384030 &unresolved_link_inputs,
40394031 &create_module.link_inputs,
......@@ -4160,7 +4152,9 @@ fn serve(
41604152
41614153 var child_pid: ?std.process.Child.Id = null;
41624154
4163 const main_progress_node = std.Progress.start(.{});
4155 const main_progress_node = std.Progress.start(io, .{});
4156 defer main_progress_node.end();
4157
41644158 const file_system_inputs = comp.file_system_inputs.?;
41654159
41664160 const IncrementalDebugServer = if (build_options.enable_debug_extensions and !builtin.single_threaded)
......@@ -4183,7 +4177,7 @@ fn serve(
41834177 defer if (comp.debugIncremental()) ids.mutex.unlock(io);
41844178
41854179 switch (hdr.tag) {
4186 .exit => return cleanExit(),
4180 .exit => return cleanExit(io),
41874181 .update => {
41884182 tracy.frameMark();
41894183 file_system_inputs.clearRetainingCapacity();
......@@ -4436,12 +4430,12 @@ fn runOrTest(
44364430 // the error message and invocation below.
44374431 if (process.can_execv and arg_mode == .run) {
44384432 // execv releases the locks; no need to destroy the Compilation here.
4439 std.debug.lockStdErr();
4433 _ = try io.lockStderr(&.{}, .no_color);
44404434 const err = process.execve(gpa, argv.items, &env_map);
4441 std.debug.unlockStdErr();
4435 io.unlockStderr();
44424436 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
44434437 const cmd = try std.mem.join(arena, " ", argv.items);
4444 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
4438 fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });
44454439 } else if (process.can_spawn) {
44464440 var child = std.process.Child.init(argv.items, gpa);
44474441 child.env_map = &env_map;
......@@ -4455,9 +4449,9 @@ fn runOrTest(
44554449 comp_destroyed.* = true;
44564450
44574451 const term_result = t: {
4458 std.debug.lockStdErr();
4459 defer std.debug.unlockStdErr();
4460 break :t child.spawnAndWait();
4452 _ = try io.lockStderr(&.{}, .no_color);
4453 defer io.unlockStderr();
4454 break :t child.spawnAndWait(io);
44614455 };
44624456 const term = term_result catch |err| {
44634457 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
......@@ -4469,7 +4463,7 @@ fn runOrTest(
44694463 switch (term) {
44704464 .Exited => |code| {
44714465 if (code == 0) {
4472 return cleanExit();
4466 return cleanExit(io);
44734467 } else {
44744468 process.exit(code);
44754469 }
......@@ -4483,7 +4477,7 @@ fn runOrTest(
44834477 switch (term) {
44844478 .Exited => |code| {
44854479 if (code == 0) {
4486 return cleanExit();
4480 return cleanExit(io);
44874481 } else {
44884482 const cmd = try std.mem.join(arena, " ", argv.items);
44894483 fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd });
......@@ -4512,6 +4506,7 @@ fn runOrTestHotSwap(
45124506 all_args: []const []const u8,
45134507 runtime_args_start: ?usize,
45144508) !std.process.Child.Id {
4509 const io = comp.io;
45154510 const lf = comp.bin_file.?;
45164511
45174512 const exe_path = switch (builtin.target.os.tag) {
......@@ -4520,7 +4515,7 @@ fn runOrTestHotSwap(
45204515 // tmp zig-cache and use it to spawn the child process. This way we are free to update
45214516 // the binary with each requested hot update.
45224517 .windows => blk: {
4523 try lf.emit.root_dir.handle.copyFile(lf.emit.sub_path, comp.dirs.local_cache.handle, lf.emit.sub_path, .{});
4518 try lf.emit.root_dir.handle.copyFile(lf.emit.sub_path, comp.dirs.local_cache.handle, lf.emit.sub_path, io, .{});
45244519 break :blk try fs.path.join(gpa, &.{ comp.dirs.local_cache.path orelse ".", lf.emit.sub_path });
45254520 },
45264521
......@@ -4593,7 +4588,7 @@ fn runOrTestHotSwap(
45934588 child.stdout_behavior = .Inherit;
45944589 child.stderr_behavior = .Inherit;
45954590
4596 try child.spawn();
4591 try child.spawn(io);
45974592
45984593 return child.id;
45994594 },
......@@ -4604,6 +4599,8 @@ const UpdateModuleError = Compilation.UpdateError || error{
46044599 /// The update caused compile errors. The error bundle has already been
46054600 /// reported to the user by being rendered to stderr.
46064601 CompileErrorsReported,
4602 /// Error occurred printing compilation errors to stderr.
4603 PrintingErrorsFailed,
46074604};
46084605fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node) UpdateModuleError!void {
46094606 try comp.update(prog_node);
......@@ -4612,7 +4609,11 @@ fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node)
46124609 defer errors.deinit(comp.gpa);
46134610
46144611 if (errors.errorMessageCount() > 0) {
4615 errors.renderToStdErr(.{}, color);
4612 const io = comp.io;
4613 errors.renderToStderr(io, .{}, color) catch |err| switch (err) {
4614 error.Canceled => |e| return e,
4615 else => return error.PrintingErrorsFailed,
4616 };
46164617 return error.CompileErrorsReported;
46174618 }
46184619}
......@@ -4665,7 +4666,7 @@ fn cmdTranslateC(
46654666 return;
46664667 } else {
46674668 const color: Color = .auto;
4668 result.errors.renderToStdErr(.{}, color);
4669 result.errors.renderToStderr(io, .{}, color) catch {};
46694670 process.exit(1);
46704671 }
46714672 }
......@@ -4680,7 +4681,7 @@ fn cmdTranslateC(
46804681 } else {
46814682 const hex_digest = Cache.binToHex(result.digest);
46824683 const out_zig_path = try fs.path.join(arena, &.{ "o", &hex_digest, translated_basename });
4683 const zig_file = comp.dirs.local_cache.handle.openFile(out_zig_path, .{}) catch |err| {
4684 const zig_file = comp.dirs.local_cache.handle.openFile(io, out_zig_path, .{}) catch |err| {
46844685 const path = comp.dirs.local_cache.path orelse ".";
46854686 fatal("unable to open cached translated zig file '{s}{s}{s}': {s}", .{
46864687 path,
......@@ -4689,12 +4690,12 @@ fn cmdTranslateC(
46894690 @errorName(err),
46904691 });
46914692 };
4692 defer zig_file.close();
4693 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
4693 defer zig_file.close(io);
4694 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
46944695 var file_reader = zig_file.reader(io, &.{});
46954696 _ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited);
46964697 try stdout_writer.interface.flush();
4697 return cleanExit();
4698 return cleanExit(io);
46984699 }
46994700}
47004701
......@@ -4728,7 +4729,7 @@ const usage_init =
47284729 \\
47294730;
47304731
4731fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4732fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void {
47324733 dev.check(.init_command);
47334734
47344735 var template: enum { example, minimal } = .example;
......@@ -4740,8 +4741,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
47404741 if (mem.eql(u8, arg, "-m") or mem.eql(u8, arg, "--minimal")) {
47414742 template = .minimal;
47424743 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
4743 try fs.File.stdout().writeAll(usage_init);
4744 return cleanExit();
4744 try Io.File.stdout().writeStreamingAll(io, usage_init);
4745 return cleanExit(io);
47454746 } else {
47464747 fatal("unrecognized parameter: '{s}'", .{arg});
47474748 }
......@@ -4759,8 +4760,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
47594760
47604761 switch (template) {
47614762 .example => {
4762 var templates = findTemplates(gpa, arena);
4763 defer templates.deinit();
4763 var templates = findTemplates(gpa, arena, io);
4764 defer templates.deinit(io);
47644765
47654766 const s = fs.path.sep_str;
47664767 const template_paths = [_][]const u8{
......@@ -4772,7 +4773,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
47724773 var ok_count: usize = 0;
47734774
47744775 for (template_paths) |template_path| {
4775 if (templates.write(arena, fs.cwd(), sanitized_root_name, template_path, fingerprint)) |_| {
4776 if (templates.write(arena, io, Io.Dir.cwd(), sanitized_root_name, template_path, fingerprint)) |_| {
47764777 std.log.info("created {s}", .{template_path});
47774778 ok_count += 1;
47784779 } else |err| switch (err) {
......@@ -4786,10 +4787,10 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
47864787 if (ok_count == template_paths.len) {
47874788 std.log.info("see `zig build --help` for a menu of options", .{});
47884789 }
4789 return cleanExit();
4790 return cleanExit(io);
47904791 },
47914792 .minimal => {
4792 writeSimpleTemplateFile(Package.Manifest.basename,
4793 writeSimpleTemplateFile(io, Package.Manifest.basename,
47934794 \\.{{
47944795 \\ .name = .{s},
47954796 \\ .version = "0.0.1",
......@@ -4806,7 +4807,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
48064807 else => fatal("failed to create '{s}': {s}", .{ Package.Manifest.basename, @errorName(err) }),
48074808 error.PathAlreadyExists => fatal("refusing to overwrite '{s}'", .{Package.Manifest.basename}),
48084809 };
4809 writeSimpleTemplateFile(Package.build_zig_basename,
4810 writeSimpleTemplateFile(io, Package.build_zig_basename,
48104811 \\const std = @import("std");
48114812 \\
48124813 \\pub fn build(b: *std.Build) void {{
......@@ -4819,11 +4820,11 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
48194820 // their `build.zig.zon` *after* writing their `build.zig`. So this one isn't fatal.
48204821 error.PathAlreadyExists => {
48214822 std.log.info("successfully populated '{s}', preserving existing '{s}'", .{ Package.Manifest.basename, Package.build_zig_basename });
4822 return cleanExit();
4823 return cleanExit(io);
48234824 },
48244825 };
48254826 std.log.info("successfully populated '{s}' and '{s}'", .{ Package.Manifest.basename, Package.build_zig_basename });
4826 return cleanExit();
4827 return cleanExit(io);
48274828 },
48284829 }
48294830}
......@@ -4894,7 +4895,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
48944895 const argv_index_exe = child_argv.items.len;
48954896 _ = try child_argv.addOne();
48964897
4897 const self_exe_path = try fs.selfExePathAlloc(arena);
4898 const self_exe_path = try process.executablePathAlloc(io, arena);
48984899 try child_argv.append(self_exe_path);
48994900
49004901 const argv_index_zig_lib_dir = child_argv.items.len;
......@@ -5075,7 +5076,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
50755076
50765077 const work_around_btrfs_bug = native_os == .linux and
50775078 EnvVar.ZIG_BTRFS_WORKAROUND.isSet();
5078 const root_prog_node = std.Progress.start(.{
5079 const root_prog_node = std.Progress.start(io, .{
50795080 .disable_printing = (color == .off),
50805081 .root_name = "Compile Build Script",
50815082 });
......@@ -5110,14 +5111,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
51105111 const paths_file = debug_libc_paths_file orelse break :lci null;
51115112 if (!build_options.enable_debug_extensions) unreachable;
51125113 const lci = try arena.create(LibCInstallation);
5113 lci.* = try .parse(arena, paths_file, &resolved_target.result);
5114 lci.* = try .parse(arena, io, paths_file, &resolved_target.result);
51145115 break :lci lci;
51155116 };
51165117
51175118 process.raiseFileDescriptorLimit();
51185119
51195120 const cwd_path = try introspect.getResolvedCwd(arena);
5120 const build_root = try findBuildRoot(arena, .{
5121 const build_root = try findBuildRoot(arena, io, .{
51215122 .cwd_path = cwd_path,
51225123 .build_file = build_file,
51235124 });
......@@ -5125,6 +5126,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
51255126 // This `init` calls `fatal` on error.
51265127 var dirs: Compilation.Directories = .init(
51275128 arena,
5129 io,
51285130 override_lib_dir,
51295131 override_global_cache_dir,
51305132 .{ .override = path: {
......@@ -5134,7 +5136,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
51345136 {},
51355137 self_exe_path,
51365138 );
5137 defer dirs.deinit();
5139 defer dirs.deinit(io);
51385140
51395141 child_argv.items[argv_index_zig_lib_dir] = dirs.zig_lib.path orelse cwd_path;
51405142 child_argv.items[argv_index_build_file] = build_root.directory.path orelse cwd_path;
......@@ -5203,8 +5205,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
52035205 .parent = root_mod,
52045206 });
52055207
5206 var cleanup_build_dir: ?fs.Dir = null;
5207 defer if (cleanup_build_dir) |*dir| dir.close();
5208 var cleanup_build_dir: ?Io.Dir = null;
5209 defer if (cleanup_build_dir) |*dir| dir.close(io);
52085210
52095211 if (dev.env.supports(.fetch_command)) {
52105212 const fetch_prog_node = root_prog_node.start("Fetch Packages", 0);
......@@ -5226,7 +5228,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
52265228 if (system_pkg_dir_path) |p| {
52275229 job_queue.global_cache = .{
52285230 .path = p,
5229 .handle = fs.cwd().openDir(p, .{}) catch |err| {
5231 .handle = Io.Dir.cwd().openDir(io, p, .{}) catch |err| {
52305232 fatal("unable to open system package directory '{s}': {s}", .{
52315233 p, @errorName(err),
52325234 });
......@@ -5285,17 +5287,18 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
52855287
52865288 if (fetch.error_bundle.root_list.items.len > 0) {
52875289 var errors = try fetch.error_bundle.toOwnedBundle("");
5288 errors.renderToStdErr(.{}, color);
5290 errors.renderToStderr(io, .{}, color) catch {};
52895291 process.exit(1);
52905292 }
52915293
5292 if (fetch_only) return cleanExit();
5294 if (fetch_only) return cleanExit(io);
52935295
52945296 var source_buf = std.array_list.Managed(u8).init(gpa);
52955297 defer source_buf.deinit();
52965298 try job_queue.createDependenciesSource(&source_buf);
52975299 const deps_mod = try createDependenciesModule(
52985300 arena,
5301 io,
52995302 source_buf.items,
53005303 root_mod,
53015304 dirs,
......@@ -5357,6 +5360,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
53575360 }
53585361 } else try createEmptyDependenciesModule(
53595362 arena,
5363 io,
53605364 root_mod,
53615365 dirs,
53625366 config,
......@@ -5415,16 +5419,15 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
54155419 child.stderr_behavior = .Inherit;
54165420
54175421 const term = t: {
5418 std.debug.lockStdErr();
5419 defer std.debug.unlockStdErr();
5420 break :t child.spawnAndWait() catch |err| {
5421 fatal("failed to spawn build runner {s}: {s}", .{ child_argv.items[0], @errorName(err) });
5422 };
5422 _ = try io.lockStderr(&.{}, .no_color);
5423 defer io.unlockStderr();
5424 break :t child.spawnAndWait(io) catch |err|
5425 fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err });
54235426 };
54245427
54255428 switch (term) {
54265429 .Exited => |code| {
5427 if (code == 0) return cleanExit();
5430 if (code == 0) return cleanExit(io);
54285431 // Indicates that the build runner has reported compile errors
54295432 // and this parent process does not need to report any further
54305433 // diagnostics.
......@@ -5437,12 +5440,12 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
54375440 // that are missing.
54385441 const s = fs.path.sep_str;
54395442 const tmp_sub_path = "tmp" ++ s ++ results_tmp_file_nonce;
5440 const stdout = dirs.local_cache.handle.readFileAlloc(tmp_sub_path, arena, .limited(50 * 1024 * 1024)) catch |err| {
5443 const stdout = dirs.local_cache.handle.readFileAlloc(io, tmp_sub_path, arena, .limited(50 * 1024 * 1024)) catch |err| {
54415444 fatal("unable to read results of configure phase from '{f}{s}': {s}", .{
54425445 dirs.local_cache, tmp_sub_path, @errorName(err),
54435446 });
54445447 };
5445 dirs.local_cache.handle.deleteFile(tmp_sub_path) catch {};
5448 dirs.local_cache.handle.deleteFile(io, tmp_sub_path) catch {};
54465449
54475450 var it = mem.splitScalar(u8, stdout, '\n');
54485451 var any_errors = false;
......@@ -5511,9 +5514,10 @@ fn jitCmd(
55115514 dev.check(.jit_command);
55125515
55135516 const color: Color = .auto;
5514 const root_prog_node = if (options.progress_node) |node| node else std.Progress.start(.{
5517 const root_prog_node = if (options.progress_node) |node| node else std.Progress.start(io, .{
55155518 .disable_printing = (color == .off),
55165519 });
5520 defer root_prog_node.end();
55175521
55185522 const target_query: std.Target.Query = .{};
55195523 const resolved_target: Package.Module.ResolvedTarget = .{
......@@ -5523,9 +5527,8 @@ fn jitCmd(
55235527 .is_explicit_dynamic_linker = false,
55245528 };
55255529
5526 const self_exe_path = fs.selfExePathAlloc(arena) catch |err| {
5527 fatal("unable to find self exe path: {s}", .{@errorName(err)});
5528 };
5530 const self_exe_path = process.executablePathAlloc(io, arena) catch |err|
5531 fatal("unable to find self exe path: {t}", .{err});
55295532
55305533 const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet())
55315534 .Debug
......@@ -5538,13 +5541,14 @@ fn jitCmd(
55385541 // This `init` calls `fatal` on error.
55395542 var dirs: Compilation.Directories = .init(
55405543 arena,
5544 io,
55415545 override_lib_dir,
55425546 override_global_cache_dir,
55435547 .global,
55445548 if (native_os == .wasi) wasi_preopens,
55455549 self_exe_path,
55465550 );
5547 defer dirs.deinit();
5551 defer dirs.deinit(io);
55485552
55495553 const thread_limit = @min(
55505554 @max(std.Thread.getCpuCount() catch 1, 1),
......@@ -5623,7 +5627,7 @@ fn jitCmd(
56235627 defer comp.destroy();
56245628
56255629 if (options.server) {
5626 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
5630 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
56275631 var server: std.zig.Server = .{
56285632 .out = &stdout_writer.interface,
56295633 .in = undefined, // won't be receiving messages
......@@ -5683,19 +5687,23 @@ fn jitCmd(
56835687 child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe;
56845688 child.stderr_behavior = .Inherit;
56855689
5686 try child.spawn();
5690 const term = t: {
5691 _ = try io.lockStderr(&.{}, .no_color);
5692 defer io.unlockStderr();
5693 try child.spawn(io);
56875694
5688 if (options.capture) |ptr| {
5689 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
5690 ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));
5691 }
5695 if (options.capture) |ptr| {
5696 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
5697 ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));
5698 }
56925699
5693 const term = try child.wait();
5700 break :t try child.wait(io);
5701 };
56945702 switch (term) {
56955703 .Exited => |code| {
56965704 if (code == 0) {
56975705 if (options.capture != null) return;
5698 return cleanExit();
5706 return cleanExit(io);
56995707 }
57005708 const cmd = try std.mem.join(arena, " ", child_argv.items);
57015709 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
......@@ -5818,9 +5826,9 @@ pub fn lldMain(
58185826const ArgIteratorResponseFile = process.ArgIteratorGeneral(.{ .comments = true, .single_quotes = true });
58195827
58205828/// Initialize the arguments from a Response File. "*.rsp"
5821fn initArgIteratorResponseFile(allocator: Allocator, resp_file_path: []const u8) !ArgIteratorResponseFile {
5829fn initArgIteratorResponseFile(allocator: Allocator, io: Io, resp_file_path: []const u8) !ArgIteratorResponseFile {
58225830 const max_bytes = 10 * 1024 * 1024; // 10 MiB of command line arguments is a reasonable limit
5823 const cmd_line = try fs.cwd().readFileAlloc(resp_file_path, allocator, .limited(max_bytes));
5831 const cmd_line = try Io.Dir.cwd().readFileAlloc(io, resp_file_path, allocator, .limited(max_bytes));
58245832 errdefer allocator.free(cmd_line);
58255833
58265834 return ArgIteratorResponseFile.initTakeOwnership(allocator, cmd_line);
......@@ -5948,7 +5956,7 @@ pub const ClangArgIterator = struct {
59485956 };
59495957 }
59505958
5951 fn next(self: *ClangArgIterator) !void {
5959 fn next(self: *ClangArgIterator, io: Io) !void {
59525960 assert(self.has_next);
59535961 assert(self.next_index < self.argv.len);
59545962 // In this state we know that the parameter we are looking at is a root parameter
......@@ -5966,10 +5974,8 @@ pub const ClangArgIterator = struct {
59665974 const arena = self.arena;
59675975 const resp_file_path = arg[1..];
59685976
5969 self.arg_iterator_response_file =
5970 initArgIteratorResponseFile(arena, resp_file_path) catch |err| {
5971 fatal("unable to read response file '{s}': {s}", .{ resp_file_path, @errorName(err) });
5972 };
5977 self.arg_iterator_response_file = initArgIteratorResponseFile(arena, io, resp_file_path) catch |err|
5978 fatal("unable to read response file '{s}': {t}", .{ resp_file_path, err });
59735979 // NOTE: The ArgIteratorResponseFile returns tokens from next() that are slices of an
59745980 // internal buffer. This internal buffer is arena allocated, so it is not cleaned up here.
59755981
......@@ -6156,8 +6162,8 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
61566162 const arg = args[i];
61576163 if (mem.startsWith(u8, arg, "-")) {
61586164 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
6159 try fs.File.stdout().writeAll(usage_ast_check);
6160 return cleanExit();
6165 try Io.File.stdout().writeStreamingAll(io, usage_ast_check);
6166 return cleanExit(io);
61616167 } else if (mem.eql(u8, arg, "-t")) {
61626168 want_output_text = true;
61636169 } else if (mem.eql(u8, arg, "--zon")) {
......@@ -6184,12 +6190,12 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
61846190 const display_path = zig_source_path orelse "<stdin>";
61856191 const source: [:0]const u8 = s: {
61866192 var f = if (zig_source_path) |p| file: {
6187 break :file fs.cwd().openFile(p, .{}) catch |err| {
6193 break :file Io.Dir.cwd().openFile(io, p, .{}) catch |err| {
61886194 fatal("unable to open file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });
61896195 };
6190 } else fs.File.stdin();
6191 defer if (zig_source_path != null) f.close();
6192 var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer);
6196 } else Io.File.stdin();
6197 defer if (zig_source_path != null) f.close(io);
6198 var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer);
61936199 break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| {
61946200 fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });
61956201 };
......@@ -6207,7 +6213,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
62076213
62086214 const tree = try Ast.parse(arena, source, mode);
62096215
6210 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
6216 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
62116217 const stdout_bw = &stdout_writer.interface;
62126218 switch (mode) {
62136219 .zig => {
......@@ -6218,7 +6224,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
62186224 try wip_errors.init(arena);
62196225 try wip_errors.addZirErrorMessages(zir, tree, source, display_path);
62206226 var error_bundle = try wip_errors.toOwnedBundle("");
6221 error_bundle.renderToStdErr(.{}, color);
6227 try error_bundle.renderToStderr(io, .{}, color);
62226228 if (zir.loweringFailed()) {
62236229 process.exit(1);
62246230 }
......@@ -6228,7 +6234,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
62286234 if (zir.hasCompileErrors()) {
62296235 process.exit(1);
62306236 } else {
6231 return cleanExit();
6237 return cleanExit(io);
62326238 }
62336239 }
62346240 if (!build_options.enable_debug_extensions) {
......@@ -6279,7 +6285,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
62796285 if (zir.hasCompileErrors()) {
62806286 process.exit(1);
62816287 } else {
6282 return cleanExit();
6288 return cleanExit(io);
62836289 }
62846290 },
62856291 .zon => {
......@@ -6289,12 +6295,12 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
62896295 try wip_errors.init(arena);
62906296 try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path);
62916297 var error_bundle = try wip_errors.toOwnedBundle("");
6292 error_bundle.renderToStdErr(.{}, color);
6298 error_bundle.renderToStderr(io, .{}, color) catch {};
62936299 process.exit(1);
62946300 }
62956301
62966302 if (!want_output_text) {
6297 return cleanExit();
6303 return cleanExit(io);
62986304 }
62996305
63006306 if (!build_options.enable_debug_extensions) {
......@@ -6303,7 +6309,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
63036309
63046310 try @import("print_zoir.zig").renderToWriter(zoir, arena, stdout_bw);
63056311 try stdout_bw.flush();
6306 return cleanExit();
6312 return cleanExit(io);
63076313 },
63086314 }
63096315}
......@@ -6330,8 +6336,8 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void {
63306336 const arg = args[i];
63316337 if (mem.startsWith(u8, arg, "-")) {
63326338 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
6333 try fs.File.stdout().writeAll(detect_cpu_usage);
6334 return cleanExit();
6339 try Io.File.stdout().writeStreamingAll(io, detect_cpu_usage);
6340 return cleanExit(io);
63356341 } else if (mem.eql(u8, arg, "--llvm")) {
63366342 use_llvm = true;
63376343 } else {
......@@ -6351,10 +6357,10 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void {
63516357 const name = llvm.GetHostCPUName() orelse fatal("LLVM could not figure out the host cpu name", .{});
63526358 const features = llvm.GetHostCPUFeatures() orelse fatal("LLVM could not figure out the host cpu feature set", .{});
63536359 const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features);
6354 try printCpu(cpu);
6360 try printCpu(io, cpu);
63556361 } else {
63566362 const host_target = std.zig.resolveTargetQueryOrFatal(io, .{});
6357 try printCpu(host_target.cpu);
6363 try printCpu(io, host_target.cpu);
63586364 }
63596365}
63606366
......@@ -6421,8 +6427,8 @@ fn detectNativeCpuWithLLVM(
64216427 return result;
64226428}
64236429
6424fn printCpu(cpu: std.Target.Cpu) !void {
6425 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
6430fn printCpu(io: Io, cpu: std.Target.Cpu) !void {
6431 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
64266432 const stdout_bw = &stdout_writer.interface;
64276433
64286434 if (cpu.model.llvm_name) |llvm_name| {
......@@ -6444,6 +6450,7 @@ fn printCpu(cpu: std.Target.Cpu) !void {
64446450fn cmdDumpLlvmInts(
64456451 gpa: Allocator,
64466452 arena: Allocator,
6453 io: Io,
64476454 args: []const []const u8,
64486455) !void {
64496456 dev.check(.llvm_ints_command);
......@@ -6471,7 +6478,7 @@ fn cmdDumpLlvmInts(
64716478 const dl = tm.createTargetDataLayout();
64726479 const context = llvm.Context.create();
64736480
6474 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
6481 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
64756482 const stdout_bw = &stdout_writer.interface;
64766483 for ([_]u16{ 1, 8, 16, 32, 64, 128, 256 }) |bits| {
64776484 const int_type = context.intType(bits);
......@@ -6480,7 +6487,7 @@ fn cmdDumpLlvmInts(
64806487 }
64816488 try stdout_bw.flush();
64826489
6483 return cleanExit();
6490 return cleanExit(io);
64846491}
64856492
64866493/// This is only enabled for debug builds.
......@@ -6491,13 +6498,13 @@ fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void {
64916498
64926499 const cache_file = args[0];
64936500
6494 var f = fs.cwd().openFile(cache_file, .{}) catch |err| {
6501 var f = Io.Dir.cwd().openFile(io, cache_file, .{}) catch |err| {
64956502 fatal("unable to open zir cache file for dumping '{s}': {s}", .{ cache_file, @errorName(err) });
64966503 };
6497 defer f.close();
6504 defer f.close(io);
64986505
64996506 const zir = try Zcu.loadZirCache(arena, io, f);
6500 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
6507 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
65016508 const stdout_bw = &stdout_writer.interface;
65026509 {
65036510 const instruction_bytes = zir.instructions.len *
......@@ -6538,18 +6545,18 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {
65386545 const new_source_path = args[1];
65396546
65406547 const old_source = source: {
6541 var f = fs.cwd().openFile(old_source_path, .{}) catch |err|
6548 var f = Io.Dir.cwd().openFile(io, old_source_path, .{}) catch |err|
65426549 fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) });
6543 defer f.close();
6544 var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer);
6550 defer f.close(io);
6551 var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer);
65456552 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|
65466553 fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) });
65476554 };
65486555 const new_source = source: {
6549 var f = fs.cwd().openFile(new_source_path, .{}) catch |err|
6556 var f = Io.Dir.cwd().openFile(io, new_source_path, .{}) catch |err|
65506557 fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) });
6551 defer f.close();
6552 var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer);
6558 defer f.close(io);
6559 var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer);
65536560 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|
65546561 fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) });
65556562 };
......@@ -6562,7 +6569,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {
65626569 try wip_errors.init(arena);
65636570 try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path);
65646571 var error_bundle = try wip_errors.toOwnedBundle("");
6565 error_bundle.renderToStdErr(.{}, color);
6572 error_bundle.renderToStderr(io, .{}, color) catch {};
65666573 process.exit(1);
65676574 }
65686575
......@@ -6574,14 +6581,14 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {
65746581 try wip_errors.init(arena);
65756582 try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path);
65766583 var error_bundle = try wip_errors.toOwnedBundle("");
6577 error_bundle.renderToStdErr(.{}, color);
6584 error_bundle.renderToStderr(io, .{}, color) catch {};
65786585 process.exit(1);
65796586 }
65806587
65816588 var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .empty;
65826589 try Zcu.mapOldZirToNew(arena, old_zir, new_zir, &inst_map);
65836590
6584 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
6591 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
65856592 const stdout_bw = &stdout_writer.interface;
65866593 {
65876594 try stdout_bw.print("Instruction mappings:\n", .{});
......@@ -6623,7 +6630,7 @@ fn warnAboutForeignBinaries(
66236630 const host_query: std.Target.Query = .{};
66246631 const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query);
66256632
6626 switch (std.zig.system.getExternalExecutor(&host_target, target, .{ .link_libc = link_libc })) {
6633 switch (std.zig.system.getExternalExecutor(io, &host_target, target, .{ .link_libc = link_libc })) {
66276634 .native => return,
66286635 .rosetta => {
66296636 const host_name = try host_target.zigTriple(arena);
......@@ -6829,6 +6836,7 @@ const ClangSearchSanitizer = struct {
68296836};
68306837
68316838fn accessFrameworkPath(
6839 io: Io,
68326840 test_path: *std.array_list.Managed(u8),
68336841 checked_paths: *std.array_list.Managed(u8),
68346842 framework_dir_path: []const u8,
......@@ -6842,7 +6850,7 @@ fn accessFrameworkPath(
68426850 framework_dir_path, framework_name, framework_name, ext,
68436851 });
68446852 try checked_paths.print("\n {s}", .{test_path.items});
6845 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
6853 Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) {
68466854 error.FileNotFound => continue,
68476855 else => |e| fatal("unable to search for {s} framework '{s}': {s}", .{
68486856 ext, test_path.items, @errorName(e),
......@@ -6912,8 +6920,8 @@ fn cmdFetch(
69126920 const arg = args[i];
69136921 if (mem.startsWith(u8, arg, "-")) {
69146922 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
6915 try fs.File.stdout().writeAll(usage_fetch);
6916 return cleanExit();
6923 try Io.File.stdout().writeStreamingAll(io, usage_fetch);
6924 return cleanExit(io);
69176925 } else if (mem.eql(u8, arg, "--global-cache-dir")) {
69186926 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
69196927 i += 1;
......@@ -6946,7 +6954,7 @@ fn cmdFetch(
69466954
69476955 try http_client.initDefaultProxies(arena);
69486956
6949 var root_prog_node = std.Progress.start(.{
6957 var root_prog_node = std.Progress.start(io, .{
69506958 .root_name = "Fetch",
69516959 });
69526960 defer root_prog_node.end();
......@@ -6954,11 +6962,11 @@ fn cmdFetch(
69546962 var global_cache_directory: Directory = l: {
69556963 const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena);
69566964 break :l .{
6957 .handle = try fs.cwd().makeOpenPath(p, .{}),
6965 .handle = try Io.Dir.cwd().createDirPathOpen(io, p, .{}),
69586966 .path = p,
69596967 };
69606968 };
6961 defer global_cache_directory.handle.close();
6969 defer global_cache_directory.handle.close(io);
69626970
69636971 var job_queue: Package.Fetch.JobQueue = .{
69646972 .io = io,
......@@ -7009,7 +7017,7 @@ fn cmdFetch(
70097017
70107018 if (fetch.error_bundle.root_list.items.len > 0) {
70117019 var errors = try fetch.error_bundle.toOwnedBundle("");
7012 errors.renderToStdErr(.{}, color);
7020 errors.renderToStderr(io, .{}, color) catch {};
70137021 process.exit(1);
70147022 }
70157023
......@@ -7021,10 +7029,10 @@ fn cmdFetch(
70217029
70227030 const name = switch (save) {
70237031 .no => {
7024 var stdout = fs.File.stdout().writerStreaming(&stdout_buffer);
7032 var stdout = Io.File.stdout().writerStreaming(io, &stdout_buffer);
70257033 try stdout.interface.print("{s}\n", .{package_hash_slice});
70267034 try stdout.interface.flush();
7027 return cleanExit();
7035 return cleanExit(io);
70287036 },
70297037 .yes, .exact => |name| name: {
70307038 if (name) |n| break :name n;
......@@ -7036,14 +7044,14 @@ fn cmdFetch(
70367044
70377045 const cwd_path = try introspect.getResolvedCwd(arena);
70387046
7039 var build_root = try findBuildRoot(arena, .{
7047 var build_root = try findBuildRoot(arena, io, .{
70407048 .cwd_path = cwd_path,
70417049 });
7042 defer build_root.deinit();
7050 defer build_root.deinit(io);
70437051
70447052 // The name to use in case the manifest file needs to be created now.
70457053 const init_root_name = fs.path.basename(build_root.directory.path orelse cwd_path);
7046 var manifest, var ast = try loadManifest(gpa, arena, .{
7054 var manifest, var ast = try loadManifest(gpa, arena, io, .{
70477055 .root_name = try sanitizeExampleName(arena, init_root_name),
70487056 .dir = build_root.directory.handle,
70497057 .color = color,
......@@ -7159,15 +7167,16 @@ fn cmdFetch(
71597167 try ast.render(gpa, &aw.writer, fixups);
71607168 const rendered = aw.written();
71617169
7162 build_root.directory.handle.writeFile(.{ .sub_path = Package.Manifest.basename, .data = rendered }) catch |err| {
7170 build_root.directory.handle.writeFile(io, .{ .sub_path = Package.Manifest.basename, .data = rendered }) catch |err| {
71637171 fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err });
71647172 };
71657173
7166 return cleanExit();
7174 return cleanExit(io);
71677175}
71687176
71697177fn createEmptyDependenciesModule(
71707178 arena: Allocator,
7179 io: Io,
71717180 main_mod: *Package.Module,
71727181 dirs: Compilation.Directories,
71737182 global_options: Compilation.Config,
......@@ -7176,6 +7185,7 @@ fn createEmptyDependenciesModule(
71767185 try Package.Fetch.JobQueue.createEmptyDependenciesSource(&source);
71777186 _ = try createDependenciesModule(
71787187 arena,
7188 io,
71797189 source.items,
71807190 main_mod,
71817191 dirs,
......@@ -7187,6 +7197,7 @@ fn createEmptyDependenciesModule(
71877197/// build runner to obtain via `@import("@dependencies")`.
71887198fn createDependenciesModule(
71897199 arena: Allocator,
7200 io: Io,
71907201 source: []const u8,
71917202 main_mod: *Package.Module,
71927203 dirs: Compilation.Directories,
......@@ -7197,9 +7208,9 @@ fn createDependenciesModule(
71977208 const rand_int = std.crypto.random.int(u64);
71987209 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
71997210 {
7200 var tmp_dir = try dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{});
7201 defer tmp_dir.close();
7202 try tmp_dir.writeFile(.{ .sub_path = basename, .data = source });
7211 var tmp_dir = try dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{});
7212 defer tmp_dir.close(io);
7213 try tmp_dir.writeFile(io, .{ .sub_path = basename, .data = source });
72037214 }
72047215
72057216 var hh: Cache.HashHelper = .{};
......@@ -7208,11 +7219,7 @@ fn createDependenciesModule(
72087219 const hex_digest = hh.final();
72097220
72107221 const o_dir_sub_path = try arena.dupe(u8, "o" ++ fs.path.sep_str ++ hex_digest);
7211 try Package.Fetch.renameTmpIntoCache(
7212 dirs.local_cache.handle,
7213 tmp_dir_sub_path,
7214 o_dir_sub_path,
7215 );
7222 try Package.Fetch.renameTmpIntoCache(io, dirs.local_cache.handle, tmp_dir_sub_path, o_dir_sub_path);
72167223
72177224 const deps_mod = try Package.Module.create(arena, .{
72187225 .paths = .{
......@@ -7232,10 +7239,10 @@ fn createDependenciesModule(
72327239const BuildRoot = struct {
72337240 directory: Cache.Directory,
72347241 build_zig_basename: []const u8,
7235 cleanup_build_dir: ?fs.Dir,
7242 cleanup_build_dir: ?Io.Dir,
72367243
7237 fn deinit(br: *BuildRoot) void {
7238 if (br.cleanup_build_dir) |*dir| dir.close();
7244 fn deinit(br: *BuildRoot, io: Io) void {
7245 if (br.cleanup_build_dir) |*dir| dir.close(io);
72397246 br.* = undefined;
72407247 }
72417248};
......@@ -7245,7 +7252,7 @@ const FindBuildRootOptions = struct {
72457252 cwd_path: ?[]const u8 = null,
72467253};
72477254
7248fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {
7255fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !BuildRoot {
72497256 const cwd_path = options.cwd_path orelse try introspect.getResolvedCwd(arena);
72507257 const build_zig_basename = if (options.build_file) |bf|
72517258 fs.path.basename(bf)
......@@ -7254,7 +7261,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {
72547261
72557262 if (options.build_file) |bf| {
72567263 if (fs.path.dirname(bf)) |dirname| {
7257 const dir = fs.cwd().openDir(dirname, .{}) catch |err| {
7264 const dir = Io.Dir.cwd().openDir(io, dirname, .{}) catch |err| {
72587265 fatal("unable to open directory to build file from argument 'build-file', '{s}': {s}", .{ dirname, @errorName(err) });
72597266 };
72607267 return .{
......@@ -7266,7 +7273,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {
72667273
72677274 return .{
72687275 .build_zig_basename = build_zig_basename,
7269 .directory = .{ .path = null, .handle = fs.cwd() },
7276 .directory = .{ .path = null, .handle = Io.Dir.cwd() },
72707277 .cleanup_build_dir = null,
72717278 };
72727279 }
......@@ -7274,8 +7281,8 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {
72747281 var dirname: []const u8 = cwd_path;
72757282 while (true) {
72767283 const joined_path = try fs.path.join(arena, &[_][]const u8{ dirname, build_zig_basename });
7277 if (fs.cwd().access(joined_path, .{})) |_| {
7278 const dir = fs.cwd().openDir(dirname, .{}) catch |err| {
7284 if (Io.Dir.cwd().access(io, joined_path, .{})) |_| {
7285 const dir = Io.Dir.cwd().openDir(io, dirname, .{}) catch |err| {
72797286 fatal("unable to open directory while searching for build.zig file, '{s}': {s}", .{ dirname, @errorName(err) });
72807287 };
72817288 return .{
......@@ -7304,17 +7311,19 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {
73047311
73057312const LoadManifestOptions = struct {
73067313 root_name: []const u8,
7307 dir: fs.Dir,
7314 dir: Io.Dir,
73087315 color: Color,
73097316};
73107317
73117318fn loadManifest(
73127319 gpa: Allocator,
73137320 arena: Allocator,
7321 io: Io,
73147322 options: LoadManifestOptions,
73157323) !struct { Package.Manifest, Ast } {
73167324 const manifest_bytes = while (true) {
73177325 break options.dir.readFileAllocOptions(
7326 io,
73187327 Package.Manifest.basename,
73197328 arena,
73207329 .limited(Package.Manifest.max_bytes),
......@@ -7322,7 +7331,7 @@ fn loadManifest(
73227331 0,
73237332 ) catch |err| switch (err) {
73247333 error.FileNotFound => {
7325 writeSimpleTemplateFile(Package.Manifest.basename,
7334 writeSimpleTemplateFile(io, Package.Manifest.basename,
73267335 \\.{{
73277336 \\ .name = .{s},
73287337 \\ .version = "{s}",
......@@ -7348,7 +7357,7 @@ fn loadManifest(
73487357 errdefer ast.deinit(gpa);
73497358
73507359 if (ast.errors.len > 0) {
7351 try std.zig.printAstErrorsToStderr(gpa, ast, Package.Manifest.basename, options.color);
7360 try std.zig.printAstErrorsToStderr(gpa, io, ast, Package.Manifest.basename, options.color);
73527361 process.exit(2);
73537362 }
73547363
......@@ -7365,7 +7374,7 @@ fn loadManifest(
73657374
73667375 var error_bundle = try wip_errors.toOwnedBundle("");
73677376 defer error_bundle.deinit(gpa);
7368 error_bundle.renderToStdErr(.{}, options.color);
7377 error_bundle.renderToStderr(io, .{}, options.color) catch {};
73697378
73707379 process.exit(2);
73717380 }
......@@ -7374,12 +7383,12 @@ fn loadManifest(
73747383
73757384const Templates = struct {
73767385 zig_lib_directory: Cache.Directory,
7377 dir: fs.Dir,
7386 dir: Io.Dir,
73787387 buffer: std.array_list.Managed(u8),
73797388
7380 fn deinit(templates: *Templates) void {
7381 templates.zig_lib_directory.handle.close();
7382 templates.dir.close();
7389 fn deinit(templates: *Templates, io: Io) void {
7390 templates.zig_lib_directory.handle.close(io);
7391 templates.dir.close(io);
73837392 templates.buffer.deinit();
73847393 templates.* = undefined;
73857394 }
......@@ -7387,20 +7396,21 @@ const Templates = struct {
73877396 fn write(
73887397 templates: *Templates,
73897398 arena: Allocator,
7390 out_dir: fs.Dir,
7399 io: Io,
7400 out_dir: Io.Dir,
73917401 root_name: []const u8,
73927402 template_path: []const u8,
73937403 fingerprint: Package.Fingerprint,
73947404 ) !void {
73957405 if (fs.path.dirname(template_path)) |dirname| {
7396 out_dir.makePath(dirname) catch |err| {
7397 fatal("unable to make path '{s}': {s}", .{ dirname, @errorName(err) });
7406 out_dir.createDirPath(io, dirname) catch |err| {
7407 fatal("unable to make path '{s}': {t}", .{ dirname, err });
73987408 };
73997409 }
74007410
74017411 const max_bytes = 10 * 1024 * 1024;
7402 const contents = templates.dir.readFileAlloc(template_path, arena, .limited(max_bytes)) catch |err| {
7403 fatal("unable to read template file '{s}': {s}", .{ template_path, @errorName(err) });
7412 const contents = templates.dir.readFileAlloc(io, template_path, arena, .limited(max_bytes)) catch |err| {
7413 fatal("unable to read template file '{s}': {t}", .{ template_path, err });
74047414 };
74057415 templates.buffer.clearRetainingCapacity();
74067416 try templates.buffer.ensureUnusedCapacity(contents.len);
......@@ -7428,39 +7438,39 @@ const Templates = struct {
74287438 i += 1;
74297439 }
74307440
7431 return out_dir.writeFile(.{
7441 return out_dir.writeFile(io, .{
74327442 .sub_path = template_path,
74337443 .data = templates.buffer.items,
74347444 .flags = .{ .exclusive = true },
74357445 });
74367446 }
74377447};
7438fn writeSimpleTemplateFile(file_name: []const u8, comptime fmt: []const u8, args: anytype) !void {
7439 const f = try fs.cwd().createFile(file_name, .{ .exclusive = true });
7440 defer f.close();
7448fn writeSimpleTemplateFile(io: Io, file_name: []const u8, comptime fmt: []const u8, args: anytype) !void {
7449 const f = try Io.Dir.cwd().createFile(io, file_name, .{ .exclusive = true });
7450 defer f.close(io);
74417451 var buf: [4096]u8 = undefined;
7442 var fw = f.writer(&buf);
7452 var fw = f.writer(io, &buf);
74437453 try fw.interface.print(fmt, args);
74447454 try fw.interface.flush();
74457455}
74467456
7447fn findTemplates(gpa: Allocator, arena: Allocator) Templates {
7457fn findTemplates(gpa: Allocator, arena: Allocator, io: Io) Templates {
74487458 const cwd_path = introspect.getResolvedCwd(arena) catch |err| {
7449 fatal("unable to get cwd: {s}", .{@errorName(err)});
7459 fatal("unable to get cwd: {t}", .{err});
74507460 };
7451 const self_exe_path = fs.selfExePathAlloc(arena) catch |err| {
7452 fatal("unable to find self exe path: {s}", .{@errorName(err)});
7461 const self_exe_path = process.executablePathAlloc(io, arena) catch |err| {
7462 fatal("unable to find self exe path: {t}", .{err});
74537463 };
7454 var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, cwd_path, self_exe_path) catch |err| {
7455 fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) });
7464 var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, io, cwd_path, self_exe_path) catch |err| {
7465 fatal("unable to find zig installation directory '{s}': {t}", .{ self_exe_path, err });
74567466 };
74577467
74587468 const s = fs.path.sep_str;
74597469 const template_sub_path = "init";
7460 const template_dir = zig_lib_directory.handle.openDir(template_sub_path, .{}) catch |err| {
7470 const template_dir = zig_lib_directory.handle.openDir(io, template_sub_path, .{}) catch |err| {
74617471 const path = zig_lib_directory.path orelse ".";
7462 fatal("unable to open zig project template directory '{s}{s}{s}': {s}", .{
7463 path, s, template_sub_path, @errorName(err),
7472 fatal("unable to open zig project template directory '{s}{s}{s}': {t}", .{
7473 path, s, template_sub_path, err,
74647474 });
74657475 };
74667476
......@@ -7574,17 +7584,18 @@ fn anyObjectLinkInputs(link_inputs: []const link.UnresolvedInput) bool {
75747584 return false;
75757585}
75767586
7577fn addLibDirectoryWarn(lib_directories: *std.ArrayList(Directory), path: []const u8) void {
7578 return addLibDirectoryWarn2(lib_directories, path, false);
7587fn addLibDirectoryWarn(io: Io, lib_directories: *std.ArrayList(Directory), path: []const u8) void {
7588 return addLibDirectoryWarn2(io, lib_directories, path, false);
75797589}
75807590
75817591fn addLibDirectoryWarn2(
7592 io: Io,
75827593 lib_directories: *std.ArrayList(Directory),
75837594 path: []const u8,
75847595 ignore_not_found: bool,
75857596) void {
75867597 lib_directories.appendAssumeCapacity(.{
7587 .handle = fs.cwd().openDir(path, .{}) catch |err| {
7598 .handle = Io.Dir.cwd().openDir(io, path, .{}) catch |err| {
75887599 if (err == error.FileNotFound and ignore_not_found) return;
75897600 warn("unable to open library directory '{s}': {s}", .{ path, @errorName(err) });
75907601 return;
src/print_env.zig+11-6
......@@ -1,13 +1,17 @@
1const std = @import("std");
21const builtin = @import("builtin");
3const build_options = @import("build_options");
4const Compilation = @import("Compilation.zig");
2
3const std = @import("std");
4const Io = std.Io;
55const Allocator = std.mem.Allocator;
66const EnvVar = std.zig.EnvVar;
77const fatal = std.process.fatal;
88
9const build_options = @import("build_options");
10const Compilation = @import("Compilation.zig");
11
912pub fn cmdEnv(
1013 arena: Allocator,
14 io: Io,
1115 out: *std.Io.Writer,
1216 args: []const []const u8,
1317 wasi_preopens: switch (builtin.target.os.tag) {
......@@ -21,20 +25,21 @@ pub fn cmdEnv(
2125
2226 const self_exe_path = switch (builtin.target.os.tag) {
2327 .wasi => args[0],
24 else => std.fs.selfExePathAlloc(arena) catch |err| {
25 fatal("unable to find zig self exe path: {s}", .{@errorName(err)});
28 else => std.process.executablePathAlloc(io, arena) catch |err| {
29 fatal("unable to find zig self exe path: {t}", .{err});
2630 },
2731 };
2832
2933 var dirs: Compilation.Directories = .init(
3034 arena,
35 io,
3136 override_lib_dir,
3237 override_global_cache_dir,
3338 .global,
3439 if (builtin.target.os.tag == .wasi) wasi_preopens,
3540 if (builtin.target.os.tag != .wasi) self_exe_path,
3641 );
37 defer dirs.deinit();
42 defer dirs.deinit(io);
3843
3944 const zig_lib_dir = dirs.zig_lib.path orelse "";
4045 const zig_std_dir = try dirs.zig_lib.join(arena, &.{"std"});
src/print_targets.zig+10-9
......@@ -1,35 +1,38 @@
11const std = @import("std");
2const Io = std.Io;
23const fs = std.fs;
34const mem = std.mem;
45const meta = std.meta;
56const fatal = std.process.fatal;
67const Allocator = std.mem.Allocator;
78const Target = std.Target;
8const target = @import("target.zig");
99const assert = std.debug.assert;
10
1011const glibc = @import("libs/glibc.zig");
1112const introspect = @import("introspect.zig");
13const target = @import("target.zig");
1214
1315pub fn cmdTargets(
1416 allocator: Allocator,
17 io: Io,
1518 args: []const []const u8,
1619 out: *std.Io.Writer,
1720 native_target: *const Target,
1821) !void {
1922 _ = args;
20 var zig_lib_directory = introspect.findZigLibDir(allocator) catch |err| {
21 fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)});
22 };
23 defer zig_lib_directory.handle.close();
23 var zig_lib_directory = introspect.findZigLibDir(allocator, io) catch |err|
24 fatal("unable to find zig installation directory: {t}", .{err});
25 defer zig_lib_directory.handle.close(io);
2426 defer allocator.free(zig_lib_directory.path.?);
2527
2628 const abilists_contents = zig_lib_directory.handle.readFileAlloc(
29 io,
2730 glibc.abilists_path,
2831 allocator,
2932 .limited(glibc.abilists_max_size),
3033 ) catch |err| switch (err) {
3134 error.OutOfMemory => return error.OutOfMemory,
32 else => fatal("unable to read " ++ glibc.abilists_path ++ ": {s}", .{@errorName(err)}),
35 else => fatal("unable to read " ++ glibc.abilists_path ++ ": {t}", .{err}),
3336 };
3437 defer allocator.free(abilists_contents);
3538
......@@ -48,9 +51,7 @@ pub fn cmdTargets(
4851 {
4952 var libc_obj = try root_obj.beginTupleField("libc", .{});
5053 for (std.zig.target.available_libcs) |libc| {
51 const tmp = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{
52 @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi),
53 });
54 const tmp = try std.fmt.allocPrint(allocator, "{t}-{t}-{t}", .{ libc.arch, libc.os, libc.abi });
5455 defer allocator.free(tmp);
5556 try libc_obj.field(tmp, .{});
5657 }
stage1/wasi.c+61
......@@ -939,6 +939,58 @@ uint32_t wasi_snapshot_preview1_path_remove_directory(uint32_t fd, uint32_t path
939939 return wasi_errno_success;
940940}
941941
942uint32_t wasi_snapshot_preview1_path_symlink(uint32_t old_path, uint32_t old_path_len, uint32_t fd, uint32_t new_path, uint32_t new_path_len) {
943 uint8_t *const m = *wasm_memory;
944 const char *old_path_ptr = (const char *)&m[old_path];
945 const char *new_path_ptr = (const char *)&m[new_path];
946#if LOG_TRACE
947 fprintf(stderr, "wasi_snapshot_preview1_path_symlink(\"%.*s\", %u, \"%.*s\")\n", (int)old_path_len, old_path_ptr, fd, (int)new_path_len, new_path_ptr);
948#endif
949 (void)old_path_ptr;
950 (void)old_path_len;
951 (void)fd;
952 (void)new_path_ptr;
953 (void)new_path_len;
954 panic("unimplemented: path_symlink");
955 return wasi_errno_success;
956}
957
958uint32_t wasi_snapshot_preview1_path_readlink(uint32_t fd, uint32_t path, uint32_t path_len, uint32_t buf, uint32_t buf_len, uint32_t out_len) {
959 uint8_t *const m = *wasm_memory;
960 const char *path_ptr = (const char *)&m[path];
961 char *buf_ptr = (char *)&m[buf];
962 uint32_t *out_len_ptr = (uint32_t *)&m[out_len];
963#if LOG_TRACE
964 fprintf(stderr, "wasi_snapshot_preview1_path_readlink(%u, \"%.*s\", 0x%X, %u, 0x%X)\n", fd, (int)path_len, path_ptr, buf, buf_len, out_len);
965#endif
966 (void)fd;
967 (void)path_ptr;
968 (void)path_len;
969 (void)buf_ptr;
970 (void)buf_len;
971 (void)out_len_ptr;
972 panic("unimplemented: path_readlink");
973 return wasi_errno_success;
974}
975
976uint32_t wasi_snapshot_preview1_path_link(uint32_t old_fd, uint32_t old_flags, uint32_t old_path, uint32_t old_path_len, uint32_t new_fd, uint32_t new_path, uint32_t new_path_len) {
977 uint8_t *const m = *wasm_memory;
978 const char *old_path_ptr = (const char *)&m[old_path];
979 const char *new_path_ptr = (const char *)&m[new_path];
980#if LOG_TRACE
981 fprintf(stderr, "wasi_snapshot_preview1_path_link(%u, 0x%X, \"%.*s\", %u, \"%.*s\")\n", old_fd, old_flags, (int)old_path_len, old_path_ptr, new_fd, (int)new_path_len, new_path_ptr);
982#endif
983 (void)old_fd;
984 (void)old_flags;
985 (void)old_path_ptr;
986 (void)old_path_len;
987 (void)new_fd;
988 (void)new_path_ptr;
989 (void)new_path_len;
990 panic("unimplemented: path_link");
991 return wasi_errno_success;
992}
993
942994uint32_t wasi_snapshot_preview1_path_unlink_file(uint32_t fd, uint32_t path, uint32_t path_len) {
943995 uint8_t *const m = *wasm_memory;
944996 const char *path_ptr = (const char *)&m[path];
......@@ -1038,6 +1090,15 @@ uint32_t wasi_snapshot_preview1_fd_seek(uint32_t fd, uint64_t in_offset, uint32_
10381090 return wasi_errno_success;
10391091}
10401092
1093uint32_t wasi_snapshot_preview1_fd_sync(uint32_t fd) {
1094#if LOG_TRACE
1095 fprintf(stderr, "wasi_snapshot_preview1_fd_sync(%u)\n", fd);
1096#endif
1097 (void)fd;
1098 panic("unimplemented: fd_sync");
1099 return wasi_errno_success;
1100}
1101
10411102uint32_t wasi_snapshot_preview1_poll_oneoff(uint32_t in, uint32_t out, uint32_t nsubscriptions, uint32_t res_nevents) {
10421103 (void)in;
10431104 (void)out;
test/cases/disable_stack_tracing.zig+3-3
......@@ -5,16 +5,16 @@ pub const std_options: std.Options = .{
55pub fn main() !void {
66 var st_buf: [8]usize = undefined;
77 var buf: [1024]u8 = undefined;
8 var stdout = std.fs.File.stdout().writer(&buf);
8 var stdout = std.Io.File.stdout().writer(std.Options.debug_io, &buf);
99
1010 const captured_st = try foo(&stdout.interface, &st_buf);
11 try std.debug.writeStackTrace(&captured_st, &stdout.interface, .no_color);
11 try std.debug.writeStackTrace(&captured_st, .{ .writer = &stdout.interface, .mode = .no_color });
1212 try stdout.interface.print("stack trace index: {d}\n", .{captured_st.index});
1313
1414 try stdout.interface.flush();
1515}
1616fn foo(w: *std.Io.Writer, st_buf: []usize) !std.builtin.StackTrace {
17 try std.debug.writeCurrentStackTrace(.{}, w, .no_color);
17 try std.debug.writeCurrentStackTrace(.{}, .{ .writer = w, .mode = .no_color });
1818 return std.debug.captureCurrentStackTrace(.{}, st_buf);
1919}
2020
test/incremental/add_decl+13-7
......@@ -7,57 +7,63 @@
77#file=main.zig
88const std = @import("std");
99pub fn main() !void {
10 try std.fs.File.stdout().writeAll(foo);
10 try std.Io.File.stdout().writeStreamingAll(io, foo);
1111}
1212const foo = "good morning\n";
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
1314#expect_stdout="good morning\n"
1415
1516#update=add new declaration
1617#file=main.zig
1718const std = @import("std");
1819pub fn main() !void {
19 try std.fs.File.stdout().writeAll(foo);
20 try std.Io.File.stdout().writeStreamingAll(io, foo);
2021}
2122const foo = "good morning\n";
2223const bar = "good evening\n";
24const io = std.Io.Threaded.global_single_threaded.ioBasic();
2325#expect_stdout="good morning\n"
2426
2527#update=reference new declaration
2628#file=main.zig
2729const std = @import("std");
2830pub fn main() !void {
29 try std.fs.File.stdout().writeAll(bar);
31 try std.Io.File.stdout().writeStreamingAll(io, bar);
3032}
3133const foo = "good morning\n";
3234const bar = "good evening\n";
35const io = std.Io.Threaded.global_single_threaded.ioBasic();
3336#expect_stdout="good evening\n"
3437
3538#update=reference missing declaration
3639#file=main.zig
3740const std = @import("std");
3841pub fn main() !void {
39 try std.fs.File.stdout().writeAll(qux);
42 try std.Io.File.stdout().writeStreamingAll(io, qux);
4043}
4144const foo = "good morning\n";
4245const bar = "good evening\n";
43#expect_error=main.zig:3:39: error: use of undeclared identifier 'qux'
46const io = std.Io.Threaded.global_single_threaded.ioBasic();
47#expect_error=main.zig:3:52: error: use of undeclared identifier 'qux'
4448
4549#update=add missing declaration
4650#file=main.zig
4751const std = @import("std");
4852pub fn main() !void {
49 try std.fs.File.stdout().writeAll(qux);
53 try std.Io.File.stdout().writeStreamingAll(io, qux);
5054}
5155const foo = "good morning\n";
5256const bar = "good evening\n";
5357const qux = "good night\n";
58const io = std.Io.Threaded.global_single_threaded.ioBasic();
5459#expect_stdout="good night\n"
5560
5661#update=remove unused declarations
5762#file=main.zig
5863const std = @import("std");
5964pub fn main() !void {
60 try std.fs.File.stdout().writeAll(qux);
65 try std.Io.File.stdout().writeStreamingAll(io, qux);
6166}
6267const qux = "good night\n";
68const io = std.Io.Threaded.global_single_threaded.ioBasic();
6369#expect_stdout="good night\n"
test/incremental/add_decl_namespaced+13-7
......@@ -7,58 +7,64 @@
77#file=main.zig
88const std = @import("std");
99pub fn main() !void {
10 try std.fs.File.stdout().writeAll(@This().foo);
10 try std.Io.File.stdout().writeStreamingAll(io, @This().foo);
1111}
1212const foo = "good morning\n";
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
1314#expect_stdout="good morning\n"
1415
1516#update=add new declaration
1617#file=main.zig
1718const std = @import("std");
1819pub fn main() !void {
19 try std.fs.File.stdout().writeAll(@This().foo);
20 try std.Io.File.stdout().writeStreamingAll(io, @This().foo);
2021}
2122const foo = "good morning\n";
2223const bar = "good evening\n";
24const io = std.Io.Threaded.global_single_threaded.ioBasic();
2325#expect_stdout="good morning\n"
2426
2527#update=reference new declaration
2628#file=main.zig
2729const std = @import("std");
2830pub fn main() !void {
29 try std.fs.File.stdout().writeAll(@This().bar);
31 try std.Io.File.stdout().writeStreamingAll(io, @This().bar);
3032}
3133const foo = "good morning\n";
3234const bar = "good evening\n";
35const io = std.Io.Threaded.global_single_threaded.ioBasic();
3336#expect_stdout="good evening\n"
3437
3538#update=reference missing declaration
3639#file=main.zig
3740const std = @import("std");
3841pub fn main() !void {
39 try std.fs.File.stdout().writeAll(@This().qux);
42 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
4043}
4144const foo = "good morning\n";
4245const bar = "good evening\n";
43#expect_error=main.zig:3:46: error: root source file struct 'main' has no member named 'qux'
46const io = std.Io.Threaded.global_single_threaded.ioBasic();
47#expect_error=main.zig:3:59: error: root source file struct 'main' has no member named 'qux'
4448#expect_error=main.zig:1:1: note: struct declared here
4549
4650#update=add missing declaration
4751#file=main.zig
4852const std = @import("std");
4953pub fn main() !void {
50 try std.fs.File.stdout().writeAll(@This().qux);
54 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
5155}
5256const foo = "good morning\n";
5357const bar = "good evening\n";
5458const qux = "good night\n";
59const io = std.Io.Threaded.global_single_threaded.ioBasic();
5560#expect_stdout="good night\n"
5661
5762#update=remove unused declarations
5863#file=main.zig
5964const std = @import("std");
6065pub fn main() !void {
61 try std.fs.File.stdout().writeAll(@This().qux);
66 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
6267}
6368const qux = "good night\n";
69const io = std.Io.Threaded.global_single_threaded.ioBasic();
6470#expect_stdout="good night\n"
test/incremental/bad_import+4-2
......@@ -8,9 +8,10 @@
88#file=main.zig
99pub fn main() !void {
1010 _ = @import("foo.zig");
11 try std.fs.File.stdout().writeAll("success\n");
11 try std.Io.File.stdout().writeStreamingAll(io, "success\n");
1212}
1313const std = @import("std");
14const io = std.Io.Threaded.global_single_threaded.ioBasic();
1415#file=foo.zig
1516comptime {
1617 _ = @import("bad.zig");
......@@ -30,7 +31,8 @@ comptime {
3031#file=main.zig
3132pub fn main() !void {
3233 //_ = @import("foo.zig");
33 try std.fs.File.stdout().writeAll("success\n");
34 try std.Io.File.stdout().writeStreamingAll(io, "success\n");
3435}
3536const std = @import("std");
37const io = std.Io.Threaded.global_single_threaded.ioBasic();
3638#expect_stdout="success\n"
test/incremental/change_embed_file+6-3
......@@ -8,8 +8,9 @@
88const std = @import("std");
99const string = @embedFile("string.txt");
1010pub fn main() !void {
11 try std.fs.File.stdout().writeAll(string);
11 try std.Io.File.stdout().writeStreamingAll(io, string);
1212}
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
1314#file=string.txt
1415Hello, World!
1516#expect_stdout="Hello, World!\n"
......@@ -28,8 +29,9 @@ Hello again, World!
2829const std = @import("std");
2930const string = @embedFile("string.txt");
3031pub fn main() !void {
31 try std.fs.File.stdout().writeAll("a hardcoded string\n");
32 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");
3233}
34const io = std.Io.Threaded.global_single_threaded.ioBasic();
3335#expect_stdout="a hardcoded string\n"
3436
3537#update=re-introduce reference to file
......@@ -37,8 +39,9 @@ pub fn main() !void {
3739const std = @import("std");
3840const string = @embedFile("string.txt");
3941pub fn main() !void {
40 try std.fs.File.stdout().writeAll(string);
42 try std.Io.File.stdout().writeStreamingAll(io, string);
4143}
44const io = std.Io.Threaded.global_single_threaded.ioBasic();
4245#expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound
4346
4447#update=recreate file
test/incremental/change_enum_tag_type+6-3
......@@ -15,10 +15,11 @@ const Foo = enum(Tag) {
1515pub fn main() !void {
1616 var val: Foo = undefined;
1717 val = .a;
18 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
18 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
1919 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
2020}
2121const std = @import("std");
22const io = std.Io.Threaded.global_single_threaded.ioBasic();
2223#expect_stdout="a\n"
2324#update=too many enum fields
2425#file=main.zig
......@@ -33,7 +34,7 @@ const Foo = enum(Tag) {
3334pub fn main() !void {
3435 var val: Foo = undefined;
3536 val = .a;
36 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
37 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
3738 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
3839}
3940comptime {
......@@ -42,6 +43,7 @@ comptime {
4243 std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag);
4344}
4445const std = @import("std");
46const io = std.Io.Threaded.global_single_threaded.ioBasic();
4547#expect_error=main.zig:7:5: error: enumeration value '4' too large for type 'u2'
4648#update=increase tag size
4749#file=main.zig
......@@ -56,8 +58,9 @@ const Foo = enum(Tag) {
5658pub fn main() !void {
5759 var val: Foo = undefined;
5860 val = .a;
59 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
61 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
6062 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
6163}
6264const std = @import("std");
65const io = std.Io.Threaded.global_single_threaded.ioBasic();
6366#expect_stdout="a\n"
test/incremental/change_exports+12-6
......@@ -17,10 +17,11 @@ pub fn main() !void {
1717 extern const bar: u32;
1818 };
1919 S.foo();
20 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
20 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
2121 try stdout_writer.interface.print("{}\n", .{S.bar});
2222}
2323const std = @import("std");
24const io = std.Io.Threaded.global_single_threaded.ioBasic();
2425#expect_stdout="123\n"
2526
2627#update=add conflict
......@@ -39,10 +40,11 @@ pub fn main() !void {
3940 extern const other: u32;
4041 };
4142 S.foo();
42 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
43 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
4344 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
4445}
4546const std = @import("std");
47const io = std.Io.Threaded.global_single_threaded.ioBasic();
4648#expect_error=main.zig:6:5: error: exported symbol collision: foo
4749#expect_error=main.zig:1:1: note: other symbol here
4850
......@@ -62,10 +64,11 @@ pub fn main() !void {
6264 extern const other: u32;
6365 };
6466 S.foo();
65 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
67 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
6668 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
6769}
6870const std = @import("std");
71const io = std.Io.Threaded.global_single_threaded.ioBasic();
6972#expect_stdout="123 456\n"
7073
7174#update=put exports in decl
......@@ -87,10 +90,11 @@ pub fn main() !void {
8790 extern const other: u32;
8891 };
8992 S.foo();
90 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
93 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
9194 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
9295}
9396const std = @import("std");
97const io = std.Io.Threaded.global_single_threaded.ioBasic();
9498#expect_stdout="123 456\n"
9599
96100#update=remove reference to exporting decl
......@@ -133,10 +137,11 @@ pub fn main() !void {
133137 extern const other: u32;
134138 };
135139 S.foo();
136 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
140 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
137141 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
138142}
139143const std = @import("std");
144const io = std.Io.Threaded.global_single_threaded.ioBasic();
140145#expect_stdout="123 456\n"
141146
142147#update=reintroduce reference to exporting decl, introducing conflict
......@@ -158,10 +163,11 @@ pub fn main() !void {
158163 extern const other: u32;
159164 };
160165 S.foo();
161 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
166 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
162167 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
163168}
164169const std = @import("std");
170const io = std.Io.Threaded.global_single_threaded.ioBasic();
165171#expect_error=main.zig:5:5: error: exported symbol collision: bar
166172#expect_error=main.zig:2:1: note: other symbol here
167173#expect_error=main.zig:6:5: error: exported symbol collision: other
test/incremental/change_fn_type+6-3
......@@ -8,10 +8,11 @@ pub fn main() !void {
88 try foo(123);
99}
1010fn foo(x: u8) !void {
11 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
11 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
1212 return stdout_writer.interface.print("{d}\n", .{x});
1313}
1414const std = @import("std");
15const io = std.Io.Threaded.global_single_threaded.ioBasic();
1516#expect_stdout="123\n"
1617
1718#update=change function type
......@@ -20,10 +21,11 @@ pub fn main() !void {
2021 try foo(123);
2122}
2223fn foo(x: i64) !void {
23 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
24 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
2425 return stdout_writer.interface.print("{d}\n", .{x});
2526}
2627const std = @import("std");
28const io = std.Io.Threaded.global_single_threaded.ioBasic();
2729#expect_stdout="123\n"
2830
2931#update=change function argument
......@@ -32,8 +34,9 @@ pub fn main() !void {
3234 try foo(-42);
3335}
3436fn foo(x: i64) !void {
35 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
37 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
3638 return stdout_writer.interface.print("{d}\n", .{x});
3739}
3840const std = @import("std");
41const io = std.Io.Threaded.global_single_threaded.ioBasic();
3942#expect_stdout="-42\n"
test/incremental/change_generic_line_number+4-2
......@@ -4,10 +4,11 @@
44#update=initial version
55#file=main.zig
66const std = @import("std");
7const io = std.Io.Threaded.global_single_threaded.ioBasic();
78fn Printer(message: []const u8) type {
89 return struct {
910 fn print() !void {
10 try std.fs.File.stdout().writeAll(message);
11 try std.Io.File.stdout().writeStreamingAll(io, message);
1112 }
1213 };
1314}
......@@ -19,11 +20,12 @@ pub fn main() !void {
1920#update=change line number
2021#file=main.zig
2122const std = @import("std");
23const io = std.Io.Threaded.global_single_threaded.ioBasic();
2224
2325fn Printer(message: []const u8) type {
2426 return struct {
2527 fn print() !void {
26 try std.fs.File.stdout().writeAll(message);
28 try std.Io.File.stdout().writeStreamingAll(io, message);
2729 }
2830 };
2931}
test/incremental/change_line_number+4-2
......@@ -5,14 +5,16 @@
55#file=main.zig
66const std = @import("std");
77pub fn main() !void {
8 try std.fs.File.stdout().writeAll("foo\n");
8 try std.Io.File.stdout().writeStreamingAll(io, "foo\n");
99}
10const io = std.Io.Threaded.global_single_threaded.ioBasic();
1011#expect_stdout="foo\n"
1112#update=change line number
1213#file=main.zig
1314const std = @import("std");
1415
1516pub fn main() !void {
16 try std.fs.File.stdout().writeAll("foo\n");
17 try std.Io.File.stdout().writeStreamingAll(io, "foo\n");
1718}
19const io = std.Io.Threaded.global_single_threaded.ioBasic();
1820#expect_stdout="foo\n"
test/incremental/change_panic_handler+6-3
......@@ -12,11 +12,12 @@ pub fn main() !u8 {
1212}
1313pub const panic = std.debug.FullPanic(myPanic);
1414fn myPanic(msg: []const u8, _: ?usize) noreturn {
15 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
15 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
1616 stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {};
1717 std.process.exit(0);
1818}
1919const std = @import("std");
20const io = std.Io.Threaded.global_single_threaded.ioBasic();
2021#expect_stdout="panic message: integer overflow\n"
2122
2223#update=change the panic handler body
......@@ -29,11 +30,12 @@ pub fn main() !u8 {
2930}
3031pub const panic = std.debug.FullPanic(myPanic);
3132fn myPanic(msg: []const u8, _: ?usize) noreturn {
32 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
33 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
3334 stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {};
3435 std.process.exit(0);
3536}
3637const std = @import("std");
38const io = std.Io.Threaded.global_single_threaded.ioBasic();
3739#expect_stdout="new panic message: integer overflow\n"
3840
3941#update=change the panic handler function value
......@@ -46,9 +48,10 @@ pub fn main() !u8 {
4648}
4749pub const panic = std.debug.FullPanic(myPanicNew);
4850fn myPanicNew(msg: []const u8, _: ?usize) noreturn {
49 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
51 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
5052 stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {};
5153 std.process.exit(0);
5254}
5355const std = @import("std");
56const io = std.Io.Threaded.global_single_threaded.ioBasic();
5457#expect_stdout="third panic message: integer overflow\n"
test/incremental/change_panic_handler_explicit+6-3
......@@ -42,11 +42,12 @@ pub const panic = struct {
4242 pub const noreturnReturned = no_panic.noreturnReturned;
4343};
4444fn myPanic(msg: []const u8, _: ?usize) noreturn {
45 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
45 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
4646 stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {};
4747 std.process.exit(0);
4848}
4949const std = @import("std");
50const io = std.Io.Threaded.global_single_threaded.ioBasic();
5051#expect_stdout="panic message: integer overflow\n"
5152
5253#update=change the panic handler body
......@@ -89,11 +90,12 @@ pub const panic = struct {
8990 pub const noreturnReturned = no_panic.noreturnReturned;
9091};
9192fn myPanic(msg: []const u8, _: ?usize) noreturn {
92 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
93 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
9394 stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {};
9495 std.process.exit(0);
9596}
9697const std = @import("std");
98const io = std.Io.Threaded.global_single_threaded.ioBasic();
9799#expect_stdout="new panic message: integer overflow\n"
98100
99101#update=change the panic handler function value
......@@ -136,9 +138,10 @@ pub const panic = struct {
136138 pub const noreturnReturned = no_panic.noreturnReturned;
137139};
138140fn myPanicNew(msg: []const u8, _: ?usize) noreturn {
139 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
141 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
140142 stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {};
141143 std.process.exit(0);
142144}
143145const std = @import("std");
146const io = std.Io.Threaded.global_single_threaded.ioBasic();
144147#expect_stdout="third panic message: integer overflow\n"
test/incremental/change_shift_op+4-2
......@@ -9,10 +9,11 @@ pub fn main() !void {
99 try foo(0x1300);
1010}
1111fn foo(x: u16) !void {
12 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
12 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
1313 try stdout_writer.interface.print("0x{x}\n", .{x << 4});
1414}
1515const std = @import("std");
16const io = std.Io.Threaded.global_single_threaded.ioBasic();
1617#expect_stdout="0x3000\n"
1718#update=change to right shift
1819#file=main.zig
......@@ -20,8 +21,9 @@ pub fn main() !void {
2021 try foo(0x1300);
2122}
2223fn foo(x: u16) !void {
23 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
24 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
2425 try stdout_writer.interface.print("0x{x}\n", .{x >> 4});
2526}
2627const std = @import("std");
28const io = std.Io.Threaded.global_single_threaded.ioBasic();
2729#expect_stdout="0x130\n"
test/incremental/change_struct_same_fields+6-3
......@@ -11,13 +11,14 @@ pub fn main() !void {
1111 try foo(&val);
1212}
1313fn foo(val: *const S) !void {
14 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
14 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
1515 try stdout_writer.interface.print(
1616 "{d} {d}\n",
1717 .{ val.x, val.y },
1818 );
1919}
2020const std = @import("std");
21const io = std.Io.Threaded.global_single_threaded.ioBasic();
2122#expect_stdout="100 200\n"
2223
2324#update=change struct layout
......@@ -28,13 +29,14 @@ pub fn main() !void {
2829 try foo(&val);
2930}
3031fn foo(val: *const S) !void {
31 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
32 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
3233 try stdout_writer.interface.print(
3334 "{d} {d}\n",
3435 .{ val.x, val.y },
3536 );
3637}
3738const std = @import("std");
39const io = std.Io.Threaded.global_single_threaded.ioBasic();
3840#expect_stdout="100 200\n"
3941
4042#update=change values
......@@ -45,11 +47,12 @@ pub fn main() !void {
4547 try foo(&val);
4648}
4749fn foo(val: *const S) !void {
48 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
50 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
4951 try stdout_writer.interface.print(
5052 "{d} {d}\n",
5153 .{ val.x, val.y },
5254 );
5355}
5456const std = @import("std");
57const io = std.Io.Threaded.global_single_threaded.ioBasic();
5558#expect_stdout="1234 5678\n"
test/incremental/change_zon_file+6-3
......@@ -8,8 +8,9 @@
88const std = @import("std");
99const message: []const u8 = @import("message.zon");
1010pub fn main() !void {
11 try std.fs.File.stdout().writeAll(message);
11 try std.Io.File.stdout().writeStreamingAll(io, message);
1212}
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
1314#file=message.zon
1415"Hello, World!\n"
1516#expect_stdout="Hello, World!\n"
......@@ -29,8 +30,9 @@ pub fn main() !void {
2930const std = @import("std");
3031const message: []const u8 = @import("message.zon");
3132pub fn main() !void {
32 try std.fs.File.stdout().writeAll("a hardcoded string\n");
33 try std.Io.File.stdout().writeStreamingAll(io, "a hardcoded string\n");
3334}
35const io = std.Io.Threaded.global_single_threaded.ioBasic();
3436#expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound
3537#expect_error=main.zig:2:37: note: file imported here
3638
......@@ -44,6 +46,7 @@ pub fn main() !void {
4446const std = @import("std");
4547const message: []const u8 = @import("message.zon");
4648pub fn main() !void {
47 try std.fs.File.stdout().writeAll(message);
49 try std.Io.File.stdout().writeStreamingAll(io, message);
4850}
51const io = std.Io.Threaded.global_single_threaded.ioBasic();
4952#expect_stdout="We're back, World!\n"
test/incremental/change_zon_file_no_result_type+2-1
......@@ -6,8 +6,9 @@
66#update=initial version
77#file=main.zig
88const std = @import("std");
9const io = std.Io.Threaded.global_single_threaded.ioBasic();
910pub fn main() !void {
10 try std.fs.File.stdout().writeAll(@import("foo.zon").message);
11 try std.Io.File.stdout().writeStreamingAll(io, @import("foo.zon").message);
1112}
1213#file=foo.zon
1314.{
test/incremental/compile_log+6-3
......@@ -8,17 +8,19 @@
88#file=main.zig
99const std = @import("std");
1010pub fn main() !void {
11 try std.fs.File.stdout().writeAll("Hello, World!\n");
11 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
1212}
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
1314#expect_stdout="Hello, World!\n"
1415
1516#update=add compile log
1617#file=main.zig
1718const std = @import("std");
1819pub fn main() !void {
19 try std.fs.File.stdout().writeAll("Hello, World!\n");
20 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
2021 @compileLog("this is a log");
2122}
23const io = std.Io.Threaded.global_single_threaded.ioBasic();
2224#expect_error=main.zig:4:5: error: found compile log statement
2325#expect_compile_log=@as(*const [13:0]u8, "this is a log")
2426
......@@ -26,6 +28,7 @@ pub fn main() !void {
2628#file=main.zig
2729const std = @import("std");
2830pub fn main() !void {
29 try std.fs.File.stdout().writeAll("Hello, World!\n");
31 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
3032}
33const io = std.Io.Threaded.global_single_threaded.ioBasic();
3134#expect_stdout="Hello, World!\n"
test/incremental/fix_astgen_failure+8-5
......@@ -10,28 +10,31 @@ pub fn main() !void {
1010}
1111#file=foo.zig
1212pub fn hello() !void {
13 try std.fs.File.stdout().writeAll("Hello, World!\n");
13 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
1414}
1515#expect_error=foo.zig:2:9: error: use of undeclared identifier 'std'
1616#update=fix the error
1717#file=foo.zig
1818const std = @import("std");
1919pub fn hello() !void {
20 try std.fs.File.stdout().writeAll("Hello, World!\n");
20 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
2121}
22const io = std.Io.Threaded.global_single_threaded.ioBasic();
2223#expect_stdout="Hello, World!\n"
2324#update=add new error
2425#file=foo.zig
2526const std = @import("std");
2627pub fn hello() !void {
27 try std.fs.File.stdout().writeAll(hello_str);
28 try std.Io.File.stdout().writeStreamingAll(io, hello_str);
2829}
29#expect_error=foo.zig:3:39: error: use of undeclared identifier 'hello_str'
30const io = std.Io.Threaded.global_single_threaded.ioBasic();
31#expect_error=foo.zig:3:52: error: use of undeclared identifier 'hello_str'
3032#update=fix the new error
3133#file=foo.zig
3234const std = @import("std");
3335const hello_str = "Hello, World! Again!\n";
3436pub fn hello() !void {
35 try std.fs.File.stdout().writeAll(hello_str);
37 try std.Io.File.stdout().writeStreamingAll(io, hello_str);
3638}
39const io = std.Io.Threaded.global_single_threaded.ioBasic();
3740#expect_stdout="Hello, World! Again!\n"
test/incremental/function_becomes_inline+6-3
......@@ -8,9 +8,10 @@ pub fn main() !void {
88 try foo();
99}
1010fn foo() !void {
11 try std.fs.File.stdout().writeAll("Hello, World!\n");
11 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
1212}
1313const std = @import("std");
14const io = std.Io.Threaded.global_single_threaded.ioBasic();
1415#expect_stdout="Hello, World!\n"
1516
1617#update=make function inline
......@@ -19,9 +20,10 @@ pub fn main() !void {
1920 try foo();
2021}
2122inline fn foo() !void {
22 try std.fs.File.stdout().writeAll("Hello, World!\n");
23 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
2324}
2425const std = @import("std");
26const io = std.Io.Threaded.global_single_threaded.ioBasic();
2527#expect_stdout="Hello, World!\n"
2628
2729#update=change string
......@@ -30,7 +32,8 @@ pub fn main() !void {
3032 try foo();
3133}
3234inline fn foo() !void {
33 try std.fs.File.stdout().writeAll("Hello, `inline` World!\n");
35 try std.Io.File.stdout().writeStreamingAll(io, "Hello, `inline` World!\n");
3436}
3537const std = @import("std");
38const io = std.Io.Threaded.global_single_threaded.ioBasic();
3639#expect_stdout="Hello, `inline` World!\n"
test/incremental/hello+4-2
......@@ -6,14 +6,16 @@
66#update=initial version
77#file=main.zig
88const std = @import("std");
9const io = std.Io.Threaded.global_single_threaded.ioBasic();
910pub fn main() !void {
10 try std.fs.File.stdout().writeAll("good morning\n");
11 try std.Io.File.stdout().writeStreamingAll(io, "good morning\n");
1112}
1213#expect_stdout="good morning\n"
1314#update=change the string
1415#file=main.zig
1516const std = @import("std");
17const io = std.Io.Threaded.global_single_threaded.ioBasic();
1618pub fn main() !void {
17 try std.fs.File.stdout().writeAll("おはようございます\n");
19 try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n");
1820}
1921#expect_stdout="おはようございます\n"
test/incremental/make_decl_pub+4-2
......@@ -12,8 +12,9 @@ pub fn main() !void {
1212#file=foo.zig
1313const std = @import("std");
1414fn hello() !void {
15 try std.fs.File.stdout().writeAll("Hello, World!\n");
15 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
1616}
17const io = std.Io.Threaded.global_single_threaded.ioBasic();
1718#expect_error=main.zig:3:12: error: 'hello' is not marked 'pub'
1819#expect_error=foo.zig:2:1: note: declared here
1920
......@@ -21,6 +22,7 @@ fn hello() !void {
2122#file=foo.zig
2223const std = @import("std");
2324pub fn hello() !void {
24 try std.fs.File.stdout().writeAll("Hello, World!\n");
25 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
2526}
27const io = std.Io.Threaded.global_single_threaded.ioBasic();
2628#expect_stdout="Hello, World!\n"
test/incremental/modify_inline_fn+4-2
......@@ -8,20 +8,22 @@
88const std = @import("std");
99pub fn main() !void {
1010 const str = getStr();
11 try std.fs.File.stdout().writeAll(str);
11 try std.Io.File.stdout().writeStreamingAll(io, str);
1212}
1313inline fn getStr() []const u8 {
1414 return "foo\n";
1515}
16const io = std.Io.Threaded.global_single_threaded.ioBasic();
1617#expect_stdout="foo\n"
1718#update=change the string
1819#file=main.zig
1920const std = @import("std");
2021pub fn main() !void {
2122 const str = getStr();
22 try std.fs.File.stdout().writeAll(str);
23 try std.Io.File.stdout().writeStreamingAll(io, str);
2324}
2425inline fn getStr() []const u8 {
2526 return "bar\n";
2627}
28const io = std.Io.Threaded.global_single_threaded.ioBasic();
2729#expect_stdout="bar\n"
test/incremental/move_src+4-2
......@@ -7,7 +7,7 @@
77#file=main.zig
88const std = @import("std");
99pub fn main() !void {
10 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
10 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
1111 try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() });
1212}
1313fn foo() u32 {
......@@ -16,13 +16,14 @@ fn foo() u32 {
1616fn bar() u32 {
1717 return 123;
1818}
19const io = std.Io.Threaded.global_single_threaded.ioBasic();
1920#expect_stdout="7 123\n"
2021
2122#update=add newline
2223#file=main.zig
2324const std = @import("std");
2425pub fn main() !void {
25 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
26 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
2627 try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() });
2728}
2829
......@@ -32,4 +33,5 @@ fn foo() u32 {
3233fn bar() u32 {
3334 return 123;
3435}
36const io = std.Io.Threaded.global_single_threaded.ioBasic();
3537#expect_stdout="8 123\n"
test/incremental/no_change_preserves_tag_names+4-2
......@@ -7,15 +7,17 @@
77#file=main.zig
88const std = @import("std");
99var some_enum: enum { first, second } = .first;
10const io = std.Io.Threaded.global_single_threaded.ioBasic();
1011pub fn main() !void {
11 try std.fs.File.stdout().writeAll(@tagName(some_enum));
12 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
1213}
1314#expect_stdout="first"
1415#update=no change
1516#file=main.zig
1617const std = @import("std");
1718var some_enum: enum { first, second } = .first;
19const io = std.Io.Threaded.global_single_threaded.ioBasic();
1820pub fn main() !void {
19 try std.fs.File.stdout().writeAll(@tagName(some_enum));
21 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
2022}
2123#expect_stdout="first"
test/incremental/recursive_function_becomes_non_recursive+7-5
......@@ -9,11 +9,12 @@ pub fn main() !void {
99 try foo(false);
1010}
1111fn foo(recurse: bool) !void {
12 const stdout = std.fs.File.stdout();
12 const stdout = std.Io.File.stdout();
1313 if (recurse) return foo(true);
14 try stdout.writeAll("non-recursive path\n");
14 try stdout.writeStreamingAll(io, "non-recursive path\n");
1515}
1616const std = @import("std");
17const io = std.Io.Threaded.global_single_threaded.ioBasic();
1718#expect_stdout="non-recursive path\n"
1819
1920#update=eliminate recursion and change argument
......@@ -22,9 +23,10 @@ pub fn main() !void {
2223 try foo(true);
2324}
2425fn foo(recurse: bool) !void {
25 const stdout = std.fs.File.stdout();
26 if (recurse) return stdout.writeAll("x==1\n");
27 try stdout.writeAll("non-recursive path\n");
26 const stdout = std.Io.File.stdout();
27 if (recurse) return stdout.writeStreamingAll(io, "x==1\n");
28 try stdout.writeStreamingAll(io, "non-recursive path\n");
2829}
2930const std = @import("std");
31const io = std.Io.Threaded.global_single_threaded.ioBasic();
3032#expect_stdout="x==1\n"
test/incremental/remove_enum_field+4-2
......@@ -10,10 +10,11 @@ const MyEnum = enum(u8) {
1010 bar = 2,
1111};
1212pub fn main() !void {
13 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
13 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
1414 try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)});
1515}
1616const std = @import("std");
17const io = std.Io.Threaded.global_single_threaded.ioBasic();
1718#expect_stdout="1\n"
1819#update=remove enum field
1920#file=main.zig
......@@ -22,9 +23,10 @@ const MyEnum = enum(u8) {
2223 bar = 2,
2324};
2425pub fn main() !void {
25 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
26 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
2627 try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)});
2728}
2829const std = @import("std");
30const io = std.Io.Threaded.global_single_threaded.ioBasic();
2931#expect_error=main.zig:7:69: error: enum 'main.MyEnum' has no member named 'foo'
3032#expect_error=main.zig:1:16: note: enum declared here
test/incremental/unreferenced_error+8-4
......@@ -7,36 +7,40 @@
77#file=main.zig
88const std = @import("std");
99pub fn main() !void {
10 try std.fs.File.stdout().writeAll(a);
10 try std.Io.File.stdout().writeStreamingAll(io, a);
1111}
1212const a = "Hello, World!\n";
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
1314#expect_stdout="Hello, World!\n"
1415
1516#update=introduce compile error
1617#file=main.zig
1718const std = @import("std");
1819pub fn main() !void {
19 try std.fs.File.stdout().writeAll(a);
20 try std.Io.File.stdout().writeStreamingAll(io, a);
2021}
2122const a = @compileError("bad a");
23const io = std.Io.Threaded.global_single_threaded.ioBasic();
2224#expect_error=main.zig:5:11: error: bad a
2325
2426#update=remove error reference
2527#file=main.zig
2628const std = @import("std");
2729pub fn main() !void {
28 try std.fs.File.stdout().writeAll(b);
30 try std.Io.File.stdout().writeStreamingAll(io, b);
2931}
3032const a = @compileError("bad a");
3133const b = "Hi there!\n";
34const io = std.Io.Threaded.global_single_threaded.ioBasic();
3235#expect_stdout="Hi there!\n"
3336
3437#update=introduce and remove reference to error
3538#file=main.zig
3639const std = @import("std");
3740pub fn main() !void {
38 try std.fs.File.stdout().writeAll(a);
41 try std.Io.File.stdout().writeStreamingAll(io, a);
3942}
4043const a = "Back to a\n";
4144const b = @compileError("bad b");
45const io = std.Io.Threaded.global_single_threaded.ioBasic();
4246#expect_stdout="Back to a\n"
test/link/bss/main.zig+1-1
......@@ -4,7 +4,7 @@ const std = @import("std");
44var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000;
55
66pub fn main() anyerror!void {
7 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
7 var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
88
99 buffer[0x10] = 1;
1010
test/link/elf.zig+2-2
......@@ -1323,7 +1323,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step {
13231323 \\extern var live_var2: i32;
13241324 \\extern fn live_fn2() void;
13251325 \\pub fn main() void {
1326 \\ var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
1326 \\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
13271327 \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail");
13281328 \\ live_fn2();
13291329 \\}
......@@ -1365,7 +1365,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step {
13651365 \\extern var live_var2: i32;
13661366 \\extern fn live_fn2() void;
13671367 \\pub fn main() void {
1368 \\ var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
1368 \\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
13691369 \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail");
13701370 \\ live_fn2();
13711371 \\}
test/link/macho.zig+4-3
......@@ -716,7 +716,7 @@ fn testHelloZig(b: *Build, opts: Options) *Step {
716716 const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
717717 \\const std = @import("std");
718718 \\pub fn main() void {
719 \\ std.fs.File.stdout().writeAll("Hello world!\n") catch @panic("fail");
719 \\ std.Io.File.stdout().writeStreamingAll(std.Options.debug_io, "Hello world!\n") catch @panic("fail");
720720 \\}
721721 });
722722
......@@ -868,9 +868,10 @@ fn testLayout(b: *Build, opts: Options) *Step {
868868}
869869
870870fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step {
871 const io = b.graph.io;
871872 const test_step = addTestStep(b, "link-directly-cpp-tbd", opts);
872873
873 const sdk = std.zig.system.darwin.getSdk(b.allocator, &opts.target.result) orelse
874 const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse
874875 @panic("macOS SDK is required to run the test");
875876
876877 const exe = addExecutable(b, opts, .{
......@@ -2371,7 +2372,7 @@ fn testTlsZig(b: *Build, opts: Options) *Step {
23712372 \\threadlocal var x: i32 = 0;
23722373 \\threadlocal var y: i32 = -1;
23732374 \\pub fn main() void {
2374 \\ var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
2375 \\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
23752376 \\ stdout_writer.interface.print("{d} {d}\n", .{x, y}) catch unreachable;
23762377 \\ x -= 1;
23772378 \\ y += 1;
test/link/wasm/extern/main.zig+1-1
......@@ -3,6 +3,6 @@ const std = @import("std");
33extern const foo: u32;
44
55pub fn main() void {
6 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
6 var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
77 stdout_writer.interface.print("Result: {d}", .{foo}) catch {};
88}
test/src/Cases.zig+18-6
......@@ -1,6 +1,8 @@
11const Cases = @This();
22const builtin = @import("builtin");
3
34const std = @import("std");
5const Io = std.Io;
46const assert = std.debug.assert;
57const Allocator = std.mem.Allocator;
68const getExternalExecutor = std.zig.system.getExternalExecutor;
......@@ -8,6 +10,7 @@ const ArrayList = std.ArrayList;
810
911gpa: Allocator,
1012arena: Allocator,
13io: Io,
1114cases: std.array_list.Managed(Case),
1215
1316pub const IncrementalCase = struct {
......@@ -313,7 +316,7 @@ pub fn addCompile(
313316/// Each file should include a test manifest as a contiguous block of comments at
314317/// the end of the file. The first line should be the test type, followed by a set of
315318/// key-value config values, followed by a blank line, then the expected output.
316pub fn addFromDir(ctx: *Cases, dir: std.fs.Dir, b: *std.Build) void {
319pub fn addFromDir(ctx: *Cases, dir: Io.Dir, b: *std.Build) void {
317320 var current_file: []const u8 = "none";
318321 ctx.addFromDirInner(dir, &current_file, b) catch |err| {
319322 std.debug.panicExtra(
......@@ -326,16 +329,17 @@ pub fn addFromDir(ctx: *Cases, dir: std.fs.Dir, b: *std.Build) void {
326329
327330fn addFromDirInner(
328331 ctx: *Cases,
329 iterable_dir: std.fs.Dir,
332 iterable_dir: Io.Dir,
330333 /// This is kept up to date with the currently being processed file so
331334 /// that if any errors occur the caller knows it happened during this file.
332335 current_file: *[]const u8,
333336 b: *std.Build,
334337) !void {
338 const io = ctx.io;
335339 var it = try iterable_dir.walk(ctx.arena);
336340 var filenames: ArrayList([]const u8) = .empty;
337341
338 while (try it.next()) |entry| {
342 while (try it.next(io)) |entry| {
339343 if (entry.kind != .file) continue;
340344
341345 // Ignore stuff such as .swp files
......@@ -347,7 +351,7 @@ fn addFromDirInner(
347351 current_file.* = filename;
348352
349353 const max_file_size = 10 * 1024 * 1024;
350 const src = try iterable_dir.readFileAllocOptions(filename, ctx.arena, .limited(max_file_size), .@"1", 0);
354 const src = try iterable_dir.readFileAllocOptions(io, filename, ctx.arena, .limited(max_file_size), .@"1", 0);
351355
352356 // Parse the manifest
353357 var manifest = try TestManifest.parse(ctx.arena, src);
......@@ -376,6 +380,12 @@ fn addFromDirInner(
376380 // Other backends don't support new liveness format
377381 continue;
378382 }
383
384 if (backend == .selfhosted and target.cpu.arch == .aarch64) {
385 // https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9182045
386 continue;
387 }
388
379389 if (backend == .selfhosted and target.os.tag == .macos and
380390 target.cpu.arch == .x86_64 and builtin.cpu.arch == .aarch64)
381391 {
......@@ -427,9 +437,10 @@ fn addFromDirInner(
427437 }
428438}
429439
430pub fn init(gpa: Allocator, arena: Allocator) Cases {
440pub fn init(gpa: Allocator, arena: Allocator, io: Io) Cases {
431441 return .{
432442 .gpa = gpa,
443 .io = io,
433444 .cases = .init(gpa),
434445 .arena = arena,
435446 };
......@@ -457,6 +468,7 @@ pub fn lowerToBuildSteps(
457468 parent_step: *std.Build.Step,
458469 options: CaseTestOptions,
459470) void {
471 const io = self.io;
460472 const host = b.resolveTargetQuery(.{});
461473 const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM");
462474
......@@ -591,7 +603,7 @@ pub fn lowerToBuildSteps(
591603 },
592604 .Execution => |expected_stdout| no_exec: {
593605 const run = if (case.target.result.ofmt == .c) run_step: {
594 if (getExternalExecutor(&host.result, &case.target.result, .{ .link_libc = true }) != .native) {
606 if (getExternalExecutor(io, &host.result, &case.target.result, .{ .link_libc = true }) != .native) {
595607 // We wouldn't be able to run the compiled C code.
596608 break :no_exec;
597609 }
test/src/convert-stack-trace.zig+5-5
......@@ -34,20 +34,20 @@ pub fn main() !void {
3434
3535 const gpa = arena;
3636
37 var threaded: std.Io.Threaded = .init(gpa);
37 var threaded: std.Io.Threaded = .init(gpa, .{});
3838 defer threaded.deinit();
3939 const io = threaded.io();
4040
4141 var read_buf: [1024]u8 = undefined;
4242 var write_buf: [1024]u8 = undefined;
4343
44 const in_file = try std.fs.cwd().openFile(args[1], .{});
45 defer in_file.close();
44 const in_file = try std.Io.Dir.cwd().openFile(io, args[1], .{});
45 defer in_file.close(io);
4646
47 const out_file: std.fs.File = .stdout();
47 const out_file: std.Io.File = .stdout();
4848
4949 var in_fr = in_file.reader(io, &read_buf);
50 var out_fw = out_file.writer(&write_buf);
50 var out_fw = out_file.writer(io, &write_buf);
5151
5252 const w = &out_fw.interface;
5353
test/standalone/child_process/child.zig+8-8
......@@ -8,7 +8,7 @@ pub fn main() !void {
88 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
99 const arena = arena_state.allocator();
1010
11 var threaded: std.Io.Threaded = .init(arena);
11 var threaded: std.Io.Threaded = .init(arena, .{});
1212 defer threaded.deinit();
1313 const io = threaded.io();
1414
......@@ -26,28 +26,28 @@ fn run(allocator: std.mem.Allocator, io: Io) !void {
2626 const hello_arg = "hello arg";
2727 const a1 = args.next() orelse unreachable;
2828 if (!std.mem.eql(u8, a1, hello_arg)) {
29 testError("first arg: '{s}'; want '{s}'", .{ a1, hello_arg });
29 testError(io, "first arg: '{s}'; want '{s}'", .{ a1, hello_arg });
3030 }
3131 if (args.next()) |a2| {
32 testError("expected only one arg; got more: {s}", .{a2});
32 testError(io, "expected only one arg; got more: {s}", .{a2});
3333 }
3434
3535 // test stdout pipe; parent verifies
36 try std.fs.File.stdout().writeAll("hello from stdout");
36 try std.Io.File.stdout().writeStreamingAll(io, "hello from stdout");
3737
3838 // test stdin pipe from parent
3939 const hello_stdin = "hello from stdin";
4040 var buf: [hello_stdin.len]u8 = undefined;
41 const stdin: std.fs.File = .stdin();
41 const stdin: std.Io.File = .stdin();
4242 var reader = stdin.reader(io, &.{});
4343 const n = try reader.interface.readSliceShort(&buf);
4444 if (!std.mem.eql(u8, buf[0..n], hello_stdin)) {
45 testError("stdin: '{s}'; want '{s}'", .{ buf[0..n], hello_stdin });
45 testError(io, "stdin: '{s}'; want '{s}'", .{ buf[0..n], hello_stdin });
4646 }
4747}
4848
49fn testError(comptime fmt: []const u8, args: anytype) void {
50 var stderr_writer = std.fs.File.stderr().writer(&.{});
49fn testError(io: Io, comptime fmt: []const u8, args: anytype) void {
50 var stderr_writer = std.Io.File.stderr().writer(io, &.{});
5151 const stderr = &stderr_writer.interface;
5252 stderr.print("CHILD TEST ERROR: ", .{}) catch {};
5353 stderr.print(fmt, args) catch {};
test/standalone/child_process/main.zig+12-11
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23
34pub fn main() !void {
45 // make sure safety checks are enabled even in release modes
......@@ -20,7 +21,7 @@ pub fn main() !void {
2021 };
2122 defer if (needs_free) gpa.free(child_path);
2223
23 var threaded: std.Io.Threaded = .init(gpa);
24 var threaded: Io.Threaded = .init(gpa, .{});
2425 defer threaded.deinit();
2526 const io = threaded.io();
2627
......@@ -28,10 +29,10 @@ pub fn main() !void {
2829 child.stdin_behavior = .Pipe;
2930 child.stdout_behavior = .Pipe;
3031 child.stderr_behavior = .Inherit;
31 try child.spawn();
32 try child.spawn(io);
3233 const child_stdin = child.stdin.?;
33 try child_stdin.writeAll("hello from stdin"); // verified in child
34 child_stdin.close();
34 try child_stdin.writeStreamingAll(io, "hello from stdin"); // verified in child
35 child_stdin.close(io);
3536 child.stdin = null;
3637
3738 const hello_stdout = "hello from stdout";
......@@ -39,30 +40,30 @@ pub fn main() !void {
3940 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
4041 const n = try stdout_reader.interface.readSliceShort(&buf);
4142 if (!std.mem.eql(u8, buf[0..n], hello_stdout)) {
42 testError("child stdout: '{s}'; want '{s}'", .{ buf[0..n], hello_stdout });
43 testError(io, "child stdout: '{s}'; want '{s}'", .{ buf[0..n], hello_stdout });
4344 }
4445
45 switch (try child.wait()) {
46 switch (try child.wait(io)) {
4647 .Exited => |code| {
4748 const child_ok_code = 42; // set by child if no test errors
4849 if (code != child_ok_code) {
49 testError("child exit code: {d}; want {d}", .{ code, child_ok_code });
50 testError(io, "child exit code: {d}; want {d}", .{ code, child_ok_code });
5051 }
5152 },
52 else => |term| testError("abnormal child exit: {}", .{term}),
53 else => |term| testError(io, "abnormal child exit: {}", .{term}),
5354 }
5455 if (parent_test_error) return error.ParentTestError;
5556
5657 // Check that FileNotFound is consistent across platforms when trying to spawn an executable that doesn't exist
5758 const missing_child_path = try std.mem.concat(gpa, u8, &.{ child_path, "_intentionally_missing" });
5859 defer gpa.free(missing_child_path);
59 try std.testing.expectError(error.FileNotFound, std.process.Child.run(.{ .allocator = gpa, .argv = &.{missing_child_path} }));
60 try std.testing.expectError(error.FileNotFound, std.process.Child.run(gpa, io, .{ .argv = &.{missing_child_path} }));
6061}
6162
6263var parent_test_error = false;
6364
64fn testError(comptime fmt: []const u8, args: anytype) void {
65 var stderr_writer = std.fs.File.stderr().writer(&.{});
65fn testError(io: Io, comptime fmt: []const u8, args: anytype) void {
66 var stderr_writer = Io.File.stderr().writer(io, &.{});
6667 const stderr = &stderr_writer.interface;
6768 stderr.print("PARENT TEST ERROR: ", .{}) catch {};
6869 stderr.print(fmt, args) catch {};
test/standalone/cmakedefine/check.zig+4-2
......@@ -9,8 +9,10 @@ pub fn main() !void {
99 const actual_path = args[1];
1010 const expected_path = args[2];
1111
12 const actual = try std.fs.cwd().readFileAlloc(actual_path, arena, .limited(1024 * 1024));
13 const expected = try std.fs.cwd().readFileAlloc(expected_path, arena, .limited(1024 * 1024));
12 const io = std.Io.Threaded.global_single_threaded.ioBasic();
13
14 const actual = try std.Io.Dir.cwd().readFileAlloc(io, actual_path, arena, .limited(1024 * 1024));
15 const expected = try std.Io.Dir.cwd().readFileAlloc(io, expected_path, arena, .limited(1024 * 1024));
1416
1517 // The actual output starts with a comment which we should strip out before comparing.
1618 const comment_str = "/* This file was generated by ConfigHeader using the Zig Build System. */\n";
test/standalone/coff_dwarf/main.zig+1-1
......@@ -11,7 +11,7 @@ pub fn main() void {
1111 var di: std.debug.SelfInfo = .init;
1212 defer di.deinit(gpa);
1313
14 var threaded: std.Io.Threaded = .init(gpa);
14 var threaded: std.Io.Threaded = .init(gpa, .{});
1515 defer threaded.deinit();
1616 const io = threaded.io();
1717
test/standalone/dirname/build.zig+6-4
......@@ -59,13 +59,15 @@ pub fn build(b: *std.Build) void {
5959
6060 // Absolute path:
6161 const abs_path = setup_abspath: {
62 // TODO this is a bad pattern, don't do this
63 const io = b.graph.io;
6264 const temp_dir = b.makeTempPath();
6365
64 var dir = std.fs.cwd().openDir(temp_dir, .{}) catch @panic("failed to open temp dir");
65 defer dir.close();
66 var dir = std.Io.Dir.cwd().openDir(io, temp_dir, .{}) catch @panic("failed to open temp dir");
67 defer dir.close(io);
6668
67 var file = dir.createFile("foo.txt", .{}) catch @panic("failed to create file");
68 file.close();
69 var file = dir.createFile(io, "foo.txt", .{}) catch @panic("failed to create file");
70 file.close(io);
6971
7072 break :setup_abspath std.Build.LazyPath{ .cwd_relative = temp_dir };
7173 };
test/standalone/dirname/exists_in.zig+5-3
......@@ -34,8 +34,10 @@ fn run(allocator: std.mem.Allocator) !void {
3434 return error.BadUsage;
3535 };
3636
37 var dir = try std.fs.cwd().openDir(dir_path, .{});
38 defer dir.close();
37 const io = std.Io.Threaded.global_single_threaded.ioBasic();
3938
40 _ = try dir.statFile(relpath);
39 var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{});
40 defer dir.close(io);
41
42 _ = try dir.statFile(io, relpath, .{});
4143}
test/standalone/dirname/touch.zig+9-7
......@@ -26,14 +26,16 @@ fn run(allocator: std.mem.Allocator) !void {
2626 return error.BadUsage;
2727 };
2828
29 const dir_path = std.fs.path.dirname(path) orelse unreachable;
30 const basename = std.fs.path.basename(path);
29 const dir_path = std.Io.Dir.path.dirname(path) orelse unreachable;
30 const basename = std.Io.Dir.path.basename(path);
3131
32 var dir = try std.fs.cwd().openDir(dir_path, .{});
33 defer dir.close();
32 const io = std.Io.Threaded.global_single_threaded.ioBasic();
3433
35 _ = dir.statFile(basename) catch {
36 var file = try dir.createFile(basename, .{});
37 file.close();
34 var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{});
35 defer dir.close(io);
36
37 _ = dir.statFile(io, basename, .{}) catch {
38 var file = try dir.createFile(io, basename, .{});
39 file.close(io);
3840 };
3941}
test/standalone/entry_point/check_differ.zig+4-2
......@@ -6,8 +6,10 @@ pub fn main() !void {
66 const args = try std.process.argsAlloc(arena);
77 if (args.len != 3) return error.BadUsage; // usage: 'check_differ <path a> <path b>'
88
9 const contents_1 = try std.fs.cwd().readFileAlloc(args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty
10 const contents_2 = try std.fs.cwd().readFileAlloc(args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty
9 const io = std.Io.Threaded.global_single_threaded.ioBasic();
10
11 const contents_1 = try std.Io.Dir.cwd().readFileAlloc(io, args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty
12 const contents_2 = try std.Io.Dir.cwd().readFileAlloc(io, args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty
1113
1214 if (std.mem.eql(u8, contents_1, contents_2)) {
1315 return error.FilesMatch;
test/standalone/env_vars/main.zig+2
......@@ -3,6 +3,8 @@ const builtin = @import("builtin");
33
44// Note: the environment variables under test are set by the build.zig
55pub fn main() !void {
6 @setEvalBranchQuota(10000);
7
68 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
79 defer _ = gpa.deinit();
810 const allocator = gpa.allocator();
test/standalone/glibc_compat/glibc_runtime_check.zig+2-2
......@@ -28,10 +28,10 @@ extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: [*]const u8) c_int;
2828
2929// PR #17034 - fstat moved between libc_nonshared and libc
3030fn checkStat() !void {
31 const cwdFd = std.fs.cwd().fd;
31 const cwd_fd = std.Io.Dir.cwd().handle;
3232
3333 var buf: [256]u8 = @splat(0);
34 var result = fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &buf, 0);
34 var result = fstatat(cwd_fd, "a_file_that_definitely_does_not_exist", &buf, 0);
3535 assert(result == -1);
3636 assert(std.posix.errno(result) == .NOENT);
3737
test/standalone/install_headers/check_exists.zig+6-4
......@@ -11,8 +11,10 @@ pub fn main() !void {
1111 var arg_it = try std.process.argsWithAllocator(arena);
1212 _ = arg_it.next();
1313
14 const cwd = std.fs.cwd();
15 const cwd_realpath = try cwd.realpathAlloc(arena, ".");
14 const io = std.Io.Threaded.global_single_threaded.ioBasic();
15
16 const cwd = std.Io.Dir.cwd();
17 const cwd_realpath = try cwd.realPathFileAlloc(io, ".", arena);
1618
1719 while (arg_it.next()) |file_path| {
1820 if (file_path.len > 0 and file_path[0] == '!') {
......@@ -20,7 +22,7 @@ pub fn main() !void {
2022 "exclusive file check '{s}{c}{s}' failed",
2123 .{ cwd_realpath, std.fs.path.sep, file_path[1..] },
2224 );
23 if (std.fs.cwd().statFile(file_path[1..])) |_| {
25 if (cwd.statFile(io, file_path[1..], .{})) |_| {
2426 return error.FileFound;
2527 } else |err| switch (err) {
2628 error.FileNotFound => {},
......@@ -31,7 +33,7 @@ pub fn main() !void {
3133 "inclusive file check '{s}{c}{s}' failed",
3234 .{ cwd_realpath, std.fs.path.sep, file_path },
3335 );
34 _ = try std.fs.cwd().statFile(file_path);
36 _ = try cwd.statFile(io, file_path, .{});
3537 }
3638 }
3739}
test/standalone/ios/build.zig+3-1
......@@ -23,7 +23,9 @@ pub fn build(b: *std.Build) void {
2323 }),
2424 });
2525
26 if (std.zig.system.darwin.getSdk(b.allocator, &target.result)) |sdk| {
26 const io = b.graph.io;
27
28 if (std.zig.system.darwin.getSdk(b.allocator, io, &target.result)) |sdk| {
2729 b.sysroot = sdk;
2830 exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) });
2931 exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) });
test/standalone/libfuzzer/main.zig+6-6
......@@ -15,13 +15,13 @@ pub fn main() !void {
1515 defer args.deinit();
1616 _ = args.skip(); // executable name
1717
18 var threaded: std.Io.Threaded = .init(gpa);
18 var threaded: std.Io.Threaded = .init(gpa, .{});
1919 defer threaded.deinit();
2020 const io = threaded.io();
2121
2222 const cache_dir_path = args.next() orelse @panic("expected cache directory path argument");
23 var cache_dir = try std.fs.cwd().openDir(cache_dir_path, .{});
24 defer cache_dir.close();
23 var cache_dir = try std.Io.Dir.cwd().openDir(io, cache_dir_path, .{});
24 defer cache_dir.close(io);
2525
2626 abi.fuzzer_init(.fromSlice(cache_dir_path));
2727 abi.fuzzer_init_test(testOne, .fromSlice("test"));
......@@ -30,8 +30,8 @@ pub fn main() !void {
3030
3131 const pc_digest = abi.fuzzer_coverage().id;
3232 const coverage_file_path = "v/" ++ std.fmt.hex(pc_digest);
33 const coverage_file = try cache_dir.openFile(coverage_file_path, .{});
34 defer coverage_file.close();
33 const coverage_file = try cache_dir.openFile(io, coverage_file_path, .{});
34 defer coverage_file.close(io);
3535
3636 var read_buf: [@sizeOf(abi.SeenPcsHeader)]u8 = undefined;
3737 var r = coverage_file.reader(io, &read_buf);
......@@ -42,6 +42,6 @@ pub fn main() !void {
4242 const expected_len = @sizeOf(abi.SeenPcsHeader) +
4343 try std.math.divCeil(usize, pcs_header.pcs_len, @bitSizeOf(usize)) * @sizeOf(usize) +
4444 pcs_header.pcs_len * @sizeOf(usize);
45 if (try coverage_file.getEndPos() != expected_len)
45 if (try coverage_file.length(io) != expected_len)
4646 return error.WrongEnd;
4747}
test/standalone/posix/cwd.zig+63-16
......@@ -1,21 +1,30 @@
1const std = @import("std");
21const builtin = @import("builtin");
32
3const std = @import("std");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;
6const assert = std.debug.assert;
7
48const path_max = std.fs.max_path_bytes;
59
610pub fn main() !void {
7 if (builtin.target.os.tag == .wasi) {
8 // WASI doesn't support changing the working directory at all.
9 return;
11 switch (builtin.target.os.tag) {
12 .wasi => return, // WASI doesn't support changing the working directory at all.
13 .windows => return, // POSIX is not implemented by Windows
14 else => {},
1015 }
1116
12 var Allocator = std.heap.DebugAllocator(.{}){};
13 const a = Allocator.allocator();
14 defer std.debug.assert(Allocator.deinit() == .ok);
17 var debug_allocator: std.heap.DebugAllocator(.{}) = .{};
18 defer assert(debug_allocator.deinit() == .ok);
19 const gpa = debug_allocator.allocator();
20
21 var threaded: std.Io.Threaded = .init(gpa, .{});
22 defer threaded.deinit();
23 const io = threaded.io();
1524
1625 try test_chdir_self();
1726 try test_chdir_absolute();
18 try test_chdir_relative(a);
27 try test_chdir_relative(gpa, io);
1928}
2029
2130// get current working directory and expect it to match given path
......@@ -46,20 +55,20 @@ fn test_chdir_absolute() !void {
4655 try expect_cwd(parent);
4756}
4857
49fn test_chdir_relative(a: std.mem.Allocator) !void {
50 var tmp = std.testing.tmpDir(.{});
51 defer tmp.cleanup();
58fn test_chdir_relative(gpa: Allocator, io: Io) !void {
59 var tmp = tmpDir(io, .{});
60 defer tmp.cleanup(io);
5261
5362 // Use the tmpDir parent_dir as the "base" for the test. Then cd into the child
54 try tmp.parent_dir.setAsCwd();
63 try std.process.setCurrentDir(io, tmp.parent_dir);
5564
5665 // Capture base working directory path, to build expected full path
5766 var base_cwd_buf: [path_max]u8 = undefined;
5867 const base_cwd = try std.posix.getcwd(base_cwd_buf[0..]);
5968
6069 const relative_dir_name = &tmp.sub_path;
61 const expected_path = try std.fs.path.resolve(a, &.{ base_cwd, relative_dir_name });
62 defer a.free(expected_path);
70 const expected_path = try std.fs.path.resolve(gpa, &.{ base_cwd, relative_dir_name });
71 defer gpa.free(expected_path);
6372
6473 // change current working directory to new test directory
6574 try std.posix.chdir(relative_dir_name);
......@@ -68,8 +77,46 @@ fn test_chdir_relative(a: std.mem.Allocator) !void {
6877 const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]);
6978
7079 // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase
71 const resolved_cwd = try std.fs.path.resolve(a, &.{new_cwd});
72 defer a.free(resolved_cwd);
80 const resolved_cwd = try std.fs.path.resolve(gpa, &.{new_cwd});
81 defer gpa.free(resolved_cwd);
7382
7483 try std.testing.expectEqualStrings(expected_path, resolved_cwd);
7584}
85
86pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir {
87 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
88 std.crypto.random.bytes(&random_bytes);
89 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
90 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
91
92 const cwd = Io.Dir.cwd();
93 var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch
94 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");
95 defer cache_dir.close(io);
96 const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch
97 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir");
98 const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch
99 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
100
101 return .{
102 .dir = dir,
103 .parent_dir = parent_dir,
104 .sub_path = sub_path,
105 };
106}
107
108pub const TmpDir = struct {
109 dir: Io.Dir,
110 parent_dir: Io.Dir,
111 sub_path: [sub_path_len]u8,
112
113 const random_bytes_count = 12;
114 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
115
116 pub fn cleanup(self: *TmpDir, io: Io) void {
117 self.dir.close(io);
118 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
119 self.parent_dir.close(io);
120 self.* = undefined;
121 }
122};
test/standalone/posix/relpaths.zig+66-67
......@@ -1,71 +1,32 @@
11// Test relative paths through POSIX APIS. These tests have to change the cwd, so
22// they shouldn't be Zig unit tests.
33
4const std = @import("std");
54const builtin = @import("builtin");
65
6const std = @import("std");
7const Io = std.Io;
8
79pub fn main() !void {
810 if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir
911
10 var Allocator = std.heap.DebugAllocator(.{}){};
11 const a = Allocator.allocator();
12 defer std.debug.assert(Allocator.deinit() == .ok);
12 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
13 const gpa = debug_allocator.allocator();
14 defer std.debug.assert(debug_allocator.deinit() == .ok);
1315
14 var tmp = std.testing.tmpDir(.{});
15 defer tmp.cleanup();
16
17 // Want to test relative paths, so cd into the tmpdir for these tests
18 try tmp.dir.setAsCwd();
19
20 try test_symlink(a, tmp);
21 try test_link(tmp);
22}
23
24fn test_symlink(a: std.mem.Allocator, tmp: std.testing.TmpDir) !void {
25 const target_name = "symlink-target";
26 const symlink_name = "symlinker";
27
28 // Create the target file
29 try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "nonsense" });
30
31 if (builtin.target.os.tag == .windows) {
32 const wtarget_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, target_name);
33 const wsymlink_name = try std.unicode.wtf8ToWtf16LeAllocZ(a, symlink_name);
34 defer a.free(wtarget_name);
35 defer a.free(wsymlink_name);
36
37 std.os.windows.CreateSymbolicLink(tmp.dir.fd, wsymlink_name, wtarget_name, false) catch |err| switch (err) {
38 // Symlink requires admin privileges on windows, so this test can legitimately fail.
39 error.AccessDenied => return,
40 else => return err,
41 };
42 } else {
43 try std.posix.symlink(target_name, symlink_name);
44 }
16 var threaded: std.Io.Threaded = .init(gpa, .{});
17 defer threaded.deinit();
18 const io = threaded.io();
4519
46 var buffer: [std.fs.max_path_bytes]u8 = undefined;
47 const given = try std.posix.readlink(symlink_name, buffer[0..]);
48 try std.testing.expectEqualStrings(target_name, given);
49}
20 var tmp = tmpDir(io, .{});
21 defer tmp.cleanup(io);
5022
51fn getLinkInfo(fd: std.posix.fd_t) !struct { std.posix.ino_t, std.posix.nlink_t } {
52 if (builtin.target.os.tag == .linux) {
53 const stx = try std.os.linux.wrapped.statx(
54 fd,
55 "",
56 std.posix.AT.EMPTY_PATH,
57 .{ .INO = true, .NLINK = true },
58 );
59 std.debug.assert(stx.mask.INO);
60 std.debug.assert(stx.mask.NLINK);
61 return .{ stx.ino, stx.nlink };
62 }
23 // Want to test relative paths, so cd into the tmpdir for these tests
24 try std.process.setCurrentDir(io, tmp.dir);
6325
64 const st = try std.posix.fstat(fd);
65 return .{ st.ino, st.nlink };
26 try test_link(io, tmp);
6627}
6728
68fn test_link(tmp: std.testing.TmpDir) !void {
29fn test_link(io: Io, tmp: TmpDir) !void {
6930 switch (builtin.target.os.tag) {
7031 .linux, .illumos => {},
7132 else => return,
......@@ -74,29 +35,67 @@ fn test_link(tmp: std.testing.TmpDir) !void {
7435 const target_name = "link-target";
7536 const link_name = "newlink";
7637
77 try tmp.dir.writeFile(.{ .sub_path = target_name, .data = "example" });
38 try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" });
7839
7940 // Test 1: create the relative link from inside tmp
80 try std.posix.link(target_name, link_name);
41 try Io.Dir.hardLink(.cwd(), target_name, .cwd(), link_name, io, .{});
8142
8243 // Verify
83 const efd = try tmp.dir.openFile(target_name, .{});
84 defer efd.close();
44 const efd = try tmp.dir.openFile(io, target_name, .{});
45 defer efd.close(io);
8546
86 const nfd = try tmp.dir.openFile(link_name, .{});
87 defer nfd.close();
47 const nfd = try tmp.dir.openFile(io, link_name, .{});
48 defer nfd.close(io);
8849
8950 {
90 const eino, _ = try getLinkInfo(efd.handle);
91 const nino, const nlink = try getLinkInfo(nfd.handle);
92 try std.testing.expectEqual(eino, nino);
93 try std.testing.expectEqual(@as(std.posix.nlink_t, 2), nlink);
51 const e_stat = try efd.stat(io);
52 const n_stat = try nfd.stat(io);
53 try std.testing.expectEqual(e_stat.inode, n_stat.inode);
54 try std.testing.expectEqual(2, n_stat.nlink);
9455 }
9556
9657 // Test 2: Remove the link and see the stats update
97 try std.posix.unlink(link_name);
58 try Io.Dir.cwd().deleteFile(io, link_name);
9859 {
99 _, const elink = try getLinkInfo(efd.handle);
100 try std.testing.expectEqual(@as(std.posix.nlink_t, 1), elink);
60 const e_stat = try efd.stat(io);
61 try std.testing.expectEqual(1, e_stat.nlink);
10162 }
10263}
64
65pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir {
66 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
67 std.crypto.random.bytes(&random_bytes);
68 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
69 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
70
71 const cwd = Io.Dir.cwd();
72 var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch
73 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");
74 defer cache_dir.close(io);
75 const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch
76 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir");
77 const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch
78 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
79
80 return .{
81 .dir = dir,
82 .parent_dir = parent_dir,
83 .sub_path = sub_path,
84 };
85}
86
87pub const TmpDir = struct {
88 dir: Io.Dir,
89 parent_dir: Io.Dir,
90 sub_path: [sub_path_len]u8,
91
92 const random_bytes_count = 12;
93 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
94
95 pub fn cleanup(self: *TmpDir, io: Io) void {
96 self.dir.close(io);
97 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
98 self.parent_dir.close(io);
99 self.* = undefined;
100 }
101};
test/standalone/run_cwd/check_file_exists.zig+3-1
......@@ -8,7 +8,9 @@ pub fn main() !void {
88 if (args.len != 2) return error.BadUsage;
99 const path = args[1];
1010
11 std.fs.cwd().access(path, .{}) catch return error.AccessFailed;
11 const io = std.Io.Threaded.global_single_threaded.ioBasic();
12
13 std.Io.Dir.cwd().access(io, path, .{}) catch return error.AccessFailed;
1214}
1315
1416const std = @import("std");
test/standalone/run_output_caching/main.zig+4-3
......@@ -1,10 +1,11 @@
11const std = @import("std");
22
33pub fn main() !void {
4 const io = std.Io.Threaded.global_single_threaded.ioBasic();
45 var args = try std.process.argsWithAllocator(std.heap.page_allocator);
56 _ = args.skip();
67 const filename = args.next().?;
7 const file = try std.fs.cwd().createFile(filename, .{});
8 defer file.close();
9 try file.writeAll(filename);
8 const file = try std.Io.Dir.cwd().createFile(io, filename, .{});
9 defer file.close(io);
10 try file.writeStreamingAll(io, filename);
1011}
test/standalone/run_output_paths/create_file.zig+4-3
......@@ -1,16 +1,17 @@
11const std = @import("std");
22
33pub fn main() !void {
4 const io = std.Io.Threaded.global_single_threaded.ioBasic();
45 var args = try std.process.argsWithAllocator(std.heap.page_allocator);
56 _ = args.skip();
67 const dir_name = args.next().?;
7 const dir = try std.fs.cwd().openDir(if (std.mem.startsWith(u8, dir_name, "--dir="))
8 const dir = try std.Io.Dir.cwd().openDir(io, if (std.mem.startsWith(u8, dir_name, "--dir="))
89 dir_name["--dir=".len..]
910 else
1011 dir_name, .{});
1112 const file_name = args.next().?;
12 const file = try dir.createFile(file_name, .{});
13 var file_writer = file.writer(&.{});
13 const file = try dir.createFile(io, file_name, .{});
14 var file_writer = file.writer(io, &.{});
1415 try file_writer.interface.print(
1516 \\{s}
1617 \\{s}
test/standalone/self_exe_symlink/build.zig-4
......@@ -9,10 +9,6 @@ pub fn build(b: *std.Build) void {
99 const optimize: std.builtin.OptimizeMode = .Debug;
1010 const target = b.graph.host;
1111
12 // The test requires getFdPath in order to to get the path of the
13 // File returned by openSelfExe
14 if (!std.os.isGetFdPathSupportedOnTarget(target.result.os)) return;
15
1612 const main = b.addExecutable(.{
1713 .name = "main",
1814 .root_module = b.createModule(.{
test/standalone/self_exe_symlink/create-symlink.zig+4-1
......@@ -14,5 +14,8 @@ pub fn main() anyerror!void {
1414 // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`.
1515 const exe_rel_path = try std.fs.path.relative(allocator, std.fs.path.dirname(symlink_path) orelse ".", exe_path);
1616 defer allocator.free(exe_rel_path);
17 try std.fs.cwd().symLink(exe_rel_path, symlink_path, .{});
17
18 const io = std.Io.Threaded.global_single_threaded.ioBasic();
19
20 try std.Io.Dir.cwd().symLink(io, exe_rel_path, symlink_path, .{});
1821}
test/standalone/self_exe_symlink/main.zig+13-8
......@@ -1,17 +1,22 @@
11const std = @import("std");
22
33pub fn main() !void {
4 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
5 defer std.debug.assert(gpa.deinit() == .ok);
6 const allocator = gpa.allocator();
4 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
5 defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks");
6 const gpa = debug_allocator.allocator();
77
8 const self_path = try std.fs.selfExePathAlloc(allocator);
9 defer allocator.free(self_path);
8 var threaded: std.Io.Threaded = .init(gpa, .{});
9 defer threaded.deinit();
10 const io = threaded.io();
11
12 const self_path = try std.process.executablePathAlloc(io, gpa);
13 defer gpa.free(self_path);
14
15 var self_exe = try std.process.openExecutable(io, .{});
16 defer self_exe.close(io);
1017
11 var self_exe = try std.fs.openSelfExe(.{});
12 defer self_exe.close();
1318 var buf: [std.fs.max_path_bytes]u8 = undefined;
14 const self_exe_path = try std.os.getFdPath(self_exe.handle, &buf);
19 const self_exe_path = buf[0..try self_exe.realPath(io, &buf)];
1520
1621 try std.testing.expectEqualStrings(self_exe_path, self_path);
1722}
test/standalone/simple/cat/main.zig+7-7
......@@ -1,5 +1,5 @@
11const std = @import("std");
2const fs = std.fs;
2const Io = std.Io;
33const mem = std.mem;
44const warn = std.log.warn;
55const fatal = std.process.fatal;
......@@ -9,7 +9,7 @@ pub fn main() !void {
99 defer arena_instance.deinit();
1010 const arena = arena_instance.allocator();
1111
12 var threaded: std.Io.Threaded = .init(arena);
12 var threaded: std.Io.Threaded = .init(arena, .{});
1313 defer threaded.deinit();
1414 const io = threaded.io();
1515
......@@ -18,11 +18,11 @@ pub fn main() !void {
1818 const exe = args[0];
1919 var catted_anything = false;
2020 var stdout_buffer: [4096]u8 = undefined;
21 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
21 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
2222 const stdout = &stdout_writer.interface;
23 var stdin_reader = fs.File.stdin().readerStreaming(io, &.{});
23 var stdin_reader = Io.File.stdin().readerStreaming(io, &.{});
2424
25 const cwd = fs.cwd();
25 const cwd = Io.Dir.cwd();
2626
2727 for (args[1..]) |arg| {
2828 if (mem.eql(u8, arg, "-")) {
......@@ -32,8 +32,8 @@ pub fn main() !void {
3232 } else if (mem.startsWith(u8, arg, "-")) {
3333 return usage(exe);
3434 } else {
35 const file = cwd.openFile(arg, .{}) catch |err| fatal("unable to open file: {t}\n", .{err});
36 defer file.close();
35 const file = cwd.openFile(io, arg, .{}) catch |err| fatal("unable to open file: {t}\n", .{err});
36 defer file.close(io);
3737
3838 catted_anything = true;
3939 var file_reader = file.reader(io, &.{});
test/standalone/simple/guess_number/main.zig+24-9
......@@ -1,10 +1,23 @@
11const builtin = @import("builtin");
22const std = @import("std");
33
4// See https://github.com/ziglang/zig/issues/24510
5// for the plan to simplify this code.
46pub fn main() !void {
5 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
7 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
8 defer _ = debug_allocator.deinit();
9 const gpa = debug_allocator.allocator();
10
11 var threaded: std.Io.Threaded = .init(gpa, .{});
12 defer threaded.deinit();
13 const io = threaded.io();
14
15 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
616 const out = &stdout_writer.interface;
7 const stdin: std.fs.File = .stdin();
17
18 var line_buffer: [20]u8 = undefined;
19 var stdin_reader: std.Io.File.Reader = .init(.stdin(), io, &line_buffer);
20 const in = &stdin_reader.interface;
821
922 try out.writeAll("Welcome to the Guess Number Game in Zig.\n");
1023
......@@ -12,13 +25,15 @@ pub fn main() !void {
1225
1326 while (true) {
1427 try out.writeAll("\nGuess a number between 1 and 100: ");
15 var line_buf: [20]u8 = undefined;
16 const amt = try stdin.read(&line_buf);
17 if (amt == line_buf.len) {
18 try out.writeAll("Input too long.\n");
19 continue;
20 }
21 const line = std.mem.trimEnd(u8, line_buf[0..amt], "\r\n");
28 const untrimmed_line = in.takeSentinel('\n') catch |err| switch (err) {
29 error.StreamTooLong => {
30 try out.writeAll("Line too long.\n");
31 _ = try in.discardDelimiterInclusive('\n');
32 continue;
33 },
34 else => |e| return e,
35 };
36 const line = std.mem.trimEnd(u8, untrimmed_line, "\r\n");
2237
2338 const guess = std.fmt.parseUnsigned(u8, line, 10) catch {
2439 try out.writeAll("Invalid number.\n");
test/standalone/simple/hello_world/hello.zig+11-1
......@@ -1,5 +1,15 @@
11const std = @import("std");
22
3// See https://github.com/ziglang/zig/issues/24510
4// for the plan to simplify this code.
35pub fn main() !void {
4 try std.fs.File.stdout().writeAll("Hello, World!\n");
6 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
7 defer _ = debug_allocator.deinit();
8 const gpa = debug_allocator.allocator();
9
10 var threaded: std.Io.Threaded = .init(gpa, .{});
11 defer threaded.deinit();
12 const io = threaded.io();
13
14 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
515}
test/standalone/windows_argv/build.zig+1-1
......@@ -67,7 +67,7 @@ pub fn build(b: *std.Build) !void {
6767
6868 // Only target the MSVC ABI if MSVC/Windows SDK is available
6969 const has_msvc = has_msvc: {
70 const sdk = std.zig.WindowsSdk.find(b.allocator, builtin.cpu.arch) catch |err| switch (err) {
70 const sdk = std.zig.WindowsSdk.find(b.allocator, b.graph.io, builtin.cpu.arch) catch |err| switch (err) {
7171 error.OutOfMemory => @panic("oom"),
7272 else => break :has_msvc false,
7373 };
test/standalone/windows_bat_args/echo-args.zig+3-1
......@@ -5,7 +5,9 @@ pub fn main() !void {
55 defer arena_state.deinit();
66 const arena = arena_state.allocator();
77
8 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
8 const io = std.Options.debug_io;
9
10 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
911 const stdout = &stdout_writer.interface;
1012 var args = try std.process.argsAlloc(arena);
1113 for (args[1..], 1..) |arg, i| {
test/standalone/windows_bat_args/fuzz.zig+59-16
......@@ -1,5 +1,7 @@
1const std = @import("std");
21const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
35const Allocator = std.mem.Allocator;
46
57pub fn main() anyerror!void {
......@@ -7,6 +9,10 @@ pub fn main() anyerror!void {
79 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
810 const gpa = debug_alloc_inst.allocator();
911
12 var threaded: Io.Threaded = .init(gpa, .{});
13 defer threaded.deinit();
14 const io = threaded.io();
15
1016 var it = try std.process.argsWithAllocator(gpa);
1117 defer it.deinit();
1218 _ = it.next() orelse unreachable; // skip binary name
......@@ -36,11 +42,11 @@ pub fn main() anyerror!void {
3642 std.debug.print("rand seed: {}\n", .{seed});
3743 }
3844
39 var tmp = std.testing.tmpDir(.{});
40 defer tmp.cleanup();
45 var tmp = tmpDir(io, .{});
46 defer tmp.cleanup(io);
4147
42 try tmp.dir.setAsCwd();
43 defer tmp.parent_dir.setAsCwd() catch {};
48 try std.process.setCurrentDir(io, tmp.dir);
49 defer std.process.setCurrentDir(io, tmp.parent_dir) catch {};
4450
4551 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
4652 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
......@@ -56,15 +62,15 @@ pub fn main() anyerror!void {
5662 const preamble_len = buf.items.len;
5763
5864 try buf.appendSlice(gpa, " %*");
59 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });
65 try tmp.dir.writeFile(io, .{ .sub_path = "args1.bat", .data = buf.items });
6066 buf.shrinkRetainingCapacity(preamble_len);
6167
6268 try buf.appendSlice(gpa, " %1 %2 %3 %4 %5 %6 %7 %8 %9");
63 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });
69 try tmp.dir.writeFile(io, .{ .sub_path = "args2.bat", .data = buf.items });
6470 buf.shrinkRetainingCapacity(preamble_len);
6571
6672 try buf.appendSlice(gpa, " \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\"");
67 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });
73 try tmp.dir.writeFile(io, .{ .sub_path = "args3.bat", .data = buf.items });
6874 buf.shrinkRetainingCapacity(preamble_len);
6975
7076 var i: u64 = 0;
......@@ -72,19 +78,19 @@ pub fn main() anyerror!void {
7278 const rand_arg = try randomArg(gpa, rand);
7379 defer gpa.free(rand_arg);
7480
75 try testExec(gpa, &.{rand_arg}, null);
81 try testExec(gpa, io, &.{rand_arg}, null);
7682
7783 i += 1;
7884 }
7985}
8086
81fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {
82 try testExecBat(gpa, "args1.bat", args, env);
83 try testExecBat(gpa, "args2.bat", args, env);
84 try testExecBat(gpa, "args3.bat", args, env);
87fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void {
88 try testExecBat(gpa, io, "args1.bat", args, env);
89 try testExecBat(gpa, io, "args2.bat", args, env);
90 try testExecBat(gpa, io, "args3.bat", args, env);
8591}
8692
87fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {
93fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {
8894 const argv = try gpa.alloc([]const u8, 1 + args.len);
8995 defer gpa.free(argv);
9096 argv[0] = bat;
......@@ -92,8 +98,7 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8
9298
9399 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
94100
95 const result = try std.process.Child.run(.{
96 .allocator = gpa,
101 const result = try std.process.Child.run(gpa, io, .{
97102 .env_map = env,
98103 .argv = argv,
99104 });
......@@ -163,3 +168,41 @@ fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 {
163168
164169 return buf.toOwnedSlice(gpa);
165170}
171
172pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir {
173 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
174 std.crypto.random.bytes(&random_bytes);
175 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
176 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
177
178 const cwd = Io.Dir.cwd();
179 var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch
180 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");
181 defer cache_dir.close(io);
182 const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch
183 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir");
184 const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch
185 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
186
187 return .{
188 .dir = dir,
189 .parent_dir = parent_dir,
190 .sub_path = sub_path,
191 };
192}
193
194pub const TmpDir = struct {
195 dir: Io.Dir,
196 parent_dir: Io.Dir,
197 sub_path: [sub_path_len]u8,
198
199 const random_bytes_count = 12;
200 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
201
202 pub fn cleanup(self: *TmpDir, io: Io) void {
203 self.dir.close(io);
204 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
205 self.parent_dir.close(io);
206 self.* = undefined;
207 }
208};
test/standalone/windows_bat_args/test.zig+98-56
......@@ -1,20 +1,25 @@
11const std = @import("std");
2const Io = std.Io;
3const Allocator = std.mem.Allocator;
24
35pub fn main() anyerror!void {
46 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;
57 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
68 const gpa = debug_alloc_inst.allocator();
79
10 var threaded: Io.Threaded = .init(gpa, .{});
11 const io = threaded.io();
12
813 var it = try std.process.argsWithAllocator(gpa);
914 defer it.deinit();
1015 _ = it.next() orelse unreachable; // skip binary name
1116 const child_exe_path_orig = it.next() orelse unreachable;
1217
13 var tmp = std.testing.tmpDir(.{});
14 defer tmp.cleanup();
18 var tmp = tmpDir(io, .{});
19 defer tmp.cleanup(io);
1520
16 try tmp.dir.setAsCwd();
17 defer tmp.parent_dir.setAsCwd() catch {};
21 try std.process.setCurrentDir(io, tmp.dir);
22 defer std.process.setCurrentDir(io, tmp.parent_dir) catch {};
1823
1924 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
2025 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
......@@ -30,53 +35,53 @@ pub fn main() anyerror!void {
3035 const preamble_len = buf.items.len;
3136
3237 try buf.appendSlice(gpa, " %*");
33 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });
38 try tmp.dir.writeFile(io, .{ .sub_path = "args1.bat", .data = buf.items });
3439 buf.shrinkRetainingCapacity(preamble_len);
3540
3641 try buf.appendSlice(gpa, " %1 %2 %3 %4 %5 %6 %7 %8 %9");
37 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });
42 try tmp.dir.writeFile(io, .{ .sub_path = "args2.bat", .data = buf.items });
3843 buf.shrinkRetainingCapacity(preamble_len);
3944
4045 try buf.appendSlice(gpa, " \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\"");
41 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });
46 try tmp.dir.writeFile(io, .{ .sub_path = "args3.bat", .data = buf.items });
4247 buf.shrinkRetainingCapacity(preamble_len);
4348
4449 // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs
45 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\x00"});
46 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\n"});
47 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\r"});
48 try testExec(gpa, &.{ "a", "b" }, null);
49 try testExec(gpa, &.{ "c is for cat", "d is for dog" }, null);
50 try testExec(gpa, &.{ "\"", " \"" }, null);
51 try testExec(gpa, &.{ "\\", "\\" }, null);
52 try testExec(gpa, &.{">file.txt"}, null);
53 try testExec(gpa, &.{"whoami.exe"}, null);
54 try testExec(gpa, &.{"&a.exe"}, null);
55 try testExec(gpa, &.{"&echo hello "}, null);
56 try testExec(gpa, &.{ "&echo hello", "&whoami", ">file.txt" }, null);
57 try testExec(gpa, &.{"!TMP!"}, null);
58 try testExec(gpa, &.{"key=value"}, null);
59 try testExec(gpa, &.{"\"key=value\""}, null);
60 try testExec(gpa, &.{"key = value"}, null);
61 try testExec(gpa, &.{"key=[\"value\"]"}, null);
62 try testExec(gpa, &.{ "", "a=b" }, null);
63 try testExec(gpa, &.{"key=\"foo bar\""}, null);
64 try testExec(gpa, &.{"key=[\"my_value]"}, null);
65 try testExec(gpa, &.{"key=[\"my_value\",\"other-value\"]"}, null);
66 try testExec(gpa, &.{"key\\=value"}, null);
67 try testExec(gpa, &.{"key=\"&whoami\""}, null);
68 try testExec(gpa, &.{"key=\"value\"=5"}, null);
69 try testExec(gpa, &.{"key=[\">file.txt\"]"}, null);
70 try testExec(gpa, &.{"%hello"}, null);
71 try testExec(gpa, &.{"%PATH%"}, null);
72 try testExec(gpa, &.{"%%cd:~,%"}, null);
73 try testExec(gpa, &.{"%PATH%PATH%"}, null);
74 try testExec(gpa, &.{"\">file.txt"}, null);
75 try testExec(gpa, &.{"abc\"&echo hello"}, null);
76 try testExec(gpa, &.{"123\">file.txt"}, null);
77 try testExec(gpa, &.{"\"&echo hello&whoami.exe"}, null);
78 try testExec(gpa, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);
79 try testExec(gpa, &.{"&whoami.exe"}, null);
50 try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\x00"});
51 try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\n"});
52 try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\r"});
53 try testExec(gpa, io, &.{ "a", "b" }, null);
54 try testExec(gpa, io, &.{ "c is for cat", "d is for dog" }, null);
55 try testExec(gpa, io, &.{ "\"", " \"" }, null);
56 try testExec(gpa, io, &.{ "\\", "\\" }, null);
57 try testExec(gpa, io, &.{">file.txt"}, null);
58 try testExec(gpa, io, &.{"whoami.exe"}, null);
59 try testExec(gpa, io, &.{"&a.exe"}, null);
60 try testExec(gpa, io, &.{"&echo hello "}, null);
61 try testExec(gpa, io, &.{ "&echo hello", "&whoami", ">file.txt" }, null);
62 try testExec(gpa, io, &.{"!TMP!"}, null);
63 try testExec(gpa, io, &.{"key=value"}, null);
64 try testExec(gpa, io, &.{"\"key=value\""}, null);
65 try testExec(gpa, io, &.{"key = value"}, null);
66 try testExec(gpa, io, &.{"key=[\"value\"]"}, null);
67 try testExec(gpa, io, &.{ "", "a=b" }, null);
68 try testExec(gpa, io, &.{"key=\"foo bar\""}, null);
69 try testExec(gpa, io, &.{"key=[\"my_value]"}, null);
70 try testExec(gpa, io, &.{"key=[\"my_value\",\"other-value\"]"}, null);
71 try testExec(gpa, io, &.{"key\\=value"}, null);
72 try testExec(gpa, io, &.{"key=\"&whoami\""}, null);
73 try testExec(gpa, io, &.{"key=\"value\"=5"}, null);
74 try testExec(gpa, io, &.{"key=[\">file.txt\"]"}, null);
75 try testExec(gpa, io, &.{"%hello"}, null);
76 try testExec(gpa, io, &.{"%PATH%"}, null);
77 try testExec(gpa, io, &.{"%%cd:~,%"}, null);
78 try testExec(gpa, io, &.{"%PATH%PATH%"}, null);
79 try testExec(gpa, io, &.{"\">file.txt"}, null);
80 try testExec(gpa, io, &.{"abc\"&echo hello"}, null);
81 try testExec(gpa, io, &.{"123\">file.txt"}, null);
82 try testExec(gpa, io, &.{"\"&echo hello&whoami.exe"}, null);
83 try testExec(gpa, io, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);
84 try testExec(gpa, io, &.{"&whoami.exe"}, null);
8085
8186 // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution.
8287 // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped
......@@ -94,14 +99,14 @@ pub fn main() anyerror!void {
9499 // > "args1.bat .. "
95100 // '"args1.bat .. "' is not recognized as an internal or external command,
96101 // operable program or batch file.
97 try std.testing.expectError(error.FileNotFound, testExecBat(gpa, "args1.bat .. ", &.{"abc"}, null));
102 try std.testing.expectError(error.FileNotFound, testExecBat(gpa, io, "args1.bat .. ", &.{"abc"}, null));
98103 const absolute_with_trailing = blk: {
99 const absolute_path = try std.fs.realpathAlloc(gpa, "args1.bat");
104 const absolute_path = try Io.Dir.cwd().realPathFileAlloc(io, "args1.bat", gpa);
100105 defer gpa.free(absolute_path);
101106 break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " });
102107 };
103108 defer gpa.free(absolute_with_trailing);
104 try std.testing.expectError(error.FileNotFound, testExecBat(gpa, absolute_with_trailing, &.{"abc"}, null));
109 try std.testing.expectError(error.FileNotFound, testExecBat(gpa, io, absolute_with_trailing, &.{"abc"}, null));
105110
106111 var env = env: {
107112 var env = try std.process.getEnvMap(gpa);
......@@ -115,23 +120,23 @@ pub fn main() anyerror!void {
115120 break :env env;
116121 };
117122 defer env.deinit();
118 try testExec(gpa, &.{"%FOO%"}, &env);
123 try testExec(gpa, io, &.{"%FOO%"}, &env);
119124
120125 // Ensure that none of the `>file.txt`s have caused file.txt to be created
121 try std.testing.expectError(error.FileNotFound, tmp.dir.access("file.txt", .{}));
126 try std.testing.expectError(error.FileNotFound, tmp.dir.access(io, "file.txt", .{}));
122127}
123128
124fn testExecError(err: anyerror, gpa: std.mem.Allocator, args: []const []const u8) !void {
125 return std.testing.expectError(err, testExec(gpa, args, null));
129fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8) !void {
130 return std.testing.expectError(err, testExec(gpa, io, args, null));
126131}
127132
128fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {
129 try testExecBat(gpa, "args1.bat", args, env);
130 try testExecBat(gpa, "args2.bat", args, env);
131 try testExecBat(gpa, "args3.bat", args, env);
133fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void {
134 try testExecBat(gpa, io, "args1.bat", args, env);
135 try testExecBat(gpa, io, "args2.bat", args, env);
136 try testExecBat(gpa, io, "args3.bat", args, env);
132137}
133138
134fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {
139fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {
135140 const argv = try gpa.alloc([]const u8, 1 + args.len);
136141 defer gpa.free(argv);
137142 argv[0] = bat;
......@@ -139,8 +144,7 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8
139144
140145 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
141146
142 const result = try std.process.Child.run(.{
143 .allocator = gpa,
147 const result = try std.process.Child.run(gpa, io, .{
144148 .env_map = env,
145149 .argv = argv,
146150 });
......@@ -160,3 +164,41 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8
160164 i += 1;
161165 }
162166}
167
168pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir {
169 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
170 std.crypto.random.bytes(&random_bytes);
171 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
172 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
173
174 const cwd = Io.Dir.cwd();
175 var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch
176 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");
177 defer cache_dir.close(io);
178 const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch
179 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir");
180 const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch
181 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
182
183 return .{
184 .dir = dir,
185 .parent_dir = parent_dir,
186 .sub_path = sub_path,
187 };
188}
189
190pub const TmpDir = struct {
191 dir: Io.Dir,
192 parent_dir: Io.Dir,
193 sub_path: [sub_path_len]u8,
194
195 const random_bytes_count = 12;
196 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
197
198 pub fn cleanup(self: *TmpDir, io: Io) void {
199 self.dir.close(io);
200 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
201 self.parent_dir.close(io);
202 self.* = undefined;
203 }
204};
test/standalone/windows_paths/relative.zig+5-1
......@@ -10,10 +10,14 @@ pub fn main() !void {
1010
1111 if (args.len < 3) return error.MissingArgs;
1212
13 var threaded: std.Io.Threaded = .init(allocator, .{});
14 defer threaded.deinit();
15 const io = threaded.io();
16
1317 const relative = try std.fs.path.relative(allocator, args[1], args[2]);
1418 defer allocator.free(relative);
1519
16 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
20 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
1721 const stdout = &stdout_writer.interface;
1822 try stdout.writeAll(relative);
1923}
test/standalone/windows_paths/test.zig+25-22
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23
34pub fn main() anyerror!void {
45 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
......@@ -9,6 +10,8 @@ pub fn main() anyerror!void {
910
1011 if (args.len < 2) return error.MissingArgs;
1112
13 const io = std.Io.Threaded.global_single_threaded.ioBasic();
14
1215 const exe_path = args[1];
1316
1417 const cwd_path = try std.process.getCwdAlloc(arena);
......@@ -33,39 +36,39 @@ pub fn main() anyerror!void {
3336
3437 // With the special =X: environment variable set, drive-relative paths that
3538 // don't match the CWD's drive letter are resolved against that env var.
36 try checkRelative(arena, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &alt_drive_env_map);
37 try checkRelative(arena, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &alt_drive_env_map);
39 try checkRelative(arena, io, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &alt_drive_env_map);
40 try checkRelative(arena, io, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &alt_drive_env_map);
3841
3942 // Without that environment variable set, drive-relative paths that don't match the
4043 // CWD's drive letter are resolved against the root of the drive.
41 try checkRelative(arena, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);
42 try checkRelative(arena, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);
44 try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);
45 try checkRelative(arena, io, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);
4346
4447 // Bare drive-relative path with no components
45 try checkRelative(arena, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &empty_env);
46 try checkRelative(arena, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &empty_env);
48 try checkRelative(arena, io, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &empty_env);
49 try checkRelative(arena, io, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &empty_env);
4750
4851 // Bare drive-relative path with no components, drive-CWD set
49 try checkRelative(arena, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &alt_drive_env_map);
50 try checkRelative(arena, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &alt_drive_env_map);
52 try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &alt_drive_env_map);
53 try checkRelative(arena, io, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &alt_drive_env_map);
5154
5255 // Bare drive-relative path relative to the CWD should be equivalent if drive-CWD is set
53 try checkRelative(arena, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, null, &alt_drive_env_map);
54 try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, null, &alt_drive_env_map);
56 try checkRelative(arena, io, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, null, &alt_drive_env_map);
57 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, null, &alt_drive_env_map);
5558
5659 // Bare drive-relative should always be equivalent to itself
57 try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map);
58 try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map);
59 try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env);
60 try checkRelative(arena, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env);
60 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map);
61 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map);
62 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env);
63 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env);
6164 }
6265
6366 if (parsed_cwd_path.kind == .unc_absolute) {
6467 const drive_abs_path = try std.fmt.allocPrint(arena, "{c}:\\foo\\bar", .{alt_drive_letter});
6568
6669 {
67 try checkRelative(arena, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, null, &empty_env);
68 try checkRelative(arena, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, null, &empty_env);
70 try checkRelative(arena, io, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, null, &empty_env);
71 try checkRelative(arena, io, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, null, &empty_env);
6972 }
7073 } else if (parsed_cwd_path.kind == .drive_absolute) {
7174 const cur_drive_letter = parsed_cwd_path.root[0];
......@@ -73,14 +76,14 @@ pub fn main() anyerror!void {
7376 const unc_cwd = try std.fmt.allocPrint(arena, "\\\\127.0.0.1\\{c}$\\{s}", .{ cur_drive_letter, path_beyond_root });
7477
7578 {
76 try checkRelative(arena, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, null, &empty_env);
77 try checkRelative(arena, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, null, &empty_env);
79 try checkRelative(arena, io, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, null, &empty_env);
80 try checkRelative(arena, io, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, null, &empty_env);
7881 }
7982 {
8083 const drive_abs = cwd_path;
8184 const drive_rel = parsed_cwd_path.root[0..2];
82 try checkRelative(arena, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);
83 try checkRelative(arena, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);
85 try checkRelative(arena, io, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);
86 try checkRelative(arena, io, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);
8487 }
8588 } else {
8689 return error.UnexpectedPathType;
......@@ -89,13 +92,13 @@ pub fn main() anyerror!void {
8992
9093fn checkRelative(
9194 allocator: std.mem.Allocator,
95 io: Io,
9296 expected_stdout: []const u8,
9397 argv: []const []const u8,
9498 cwd: ?[]const u8,
9599 env_map: ?*const std.process.EnvMap,
96100) !void {
97 const result = try std.process.Child.run(.{
98 .allocator = allocator,
101 const result = try std.process.Child.run(allocator, io, .{
99102 .argv = argv,
100103 .cwd = cwd,
101104 .env_map = env_map,
test/standalone/windows_spawn/hello.zig+2-1
......@@ -1,7 +1,8 @@
11const std = @import("std");
22
33pub fn main() !void {
4 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
4 const io = std.Options.debug_io;
5 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
56 const stdout = &stdout_writer.interface;
67 try stdout.writeAll("hello from exe\n");
78}
test/standalone/windows_spawn/main.zig+119-76
......@@ -1,29 +1,35 @@
11const std = @import("std");
2const Io = std.Io;
3const Allocator = std.mem.Allocator;
24
35const windows = std.os.windows;
46const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral;
57
68pub fn main() anyerror!void {
7 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
8 defer if (gpa.deinit() == .leak) @panic("found memory leaks");
9 const allocator = gpa.allocator();
9 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
10 defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks");
11 const gpa = debug_allocator.allocator();
1012
11 var it = try std.process.argsWithAllocator(allocator);
13 var threaded: std.Io.Threaded = .init(gpa, .{});
14 defer threaded.deinit();
15 const io = threaded.io();
16
17 var it = try std.process.argsWithAllocator(gpa);
1218 defer it.deinit();
1319 _ = it.next() orelse unreachable; // skip binary name
1420 const hello_exe_cache_path = it.next() orelse unreachable;
1521
16 var tmp = std.testing.tmpDir(.{});
17 defer tmp.cleanup();
22 var tmp = tmpDir(io, .{});
23 defer tmp.cleanup(io);
1824
19 const tmp_absolute_path = try tmp.dir.realpathAlloc(allocator, ".");
20 defer allocator.free(tmp_absolute_path);
21 const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(allocator, tmp_absolute_path);
22 defer allocator.free(tmp_absolute_path_w);
23 const cwd_absolute_path = try std.fs.cwd().realpathAlloc(allocator, ".");
24 defer allocator.free(cwd_absolute_path);
25 const tmp_relative_path = try std.fs.path.relative(allocator, cwd_absolute_path, tmp_absolute_path);
26 defer allocator.free(tmp_relative_path);
25 const tmp_absolute_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
26 defer gpa.free(tmp_absolute_path);
27 const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(gpa, tmp_absolute_path);
28 defer gpa.free(tmp_absolute_path_w);
29 const cwd_absolute_path = try Io.Dir.cwd().realPathFileAlloc(io, ".", gpa);
30 defer gpa.free(cwd_absolute_path);
31 const tmp_relative_path = try std.fs.path.relative(gpa, cwd_absolute_path, tmp_absolute_path);
32 defer gpa.free(tmp_relative_path);
2733
2834 // Clear PATH
2935 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
......@@ -38,10 +44,10 @@ pub fn main() anyerror!void {
3844 ) == windows.TRUE);
3945
4046 // No PATH, so it should fail to find anything not in the cwd
41 try testExecError(error.FileNotFound, allocator, "something_missing");
47 try testExecError(error.FileNotFound, gpa, io, "something_missing");
4248
4349 // make sure we don't get error.BadPath traversing out of cwd with a relative path
44 try testExecError(error.FileNotFound, allocator, "..\\.\\.\\.\\\\..\\more_missing");
50 try testExecError(error.FileNotFound, gpa, io, "..\\.\\.\\.\\\\..\\more_missing");
4551
4652 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
4753 utf16Literal("PATH"),
......@@ -49,82 +55,82 @@ pub fn main() anyerror!void {
4955 ) == windows.TRUE);
5056
5157 // Move hello.exe into the tmp dir which is now added to the path
52 try std.fs.cwd().copyFile(hello_exe_cache_path, tmp.dir, "hello.exe", .{});
58 try Io.Dir.cwd().copyFile(hello_exe_cache_path, tmp.dir, "hello.exe", io, .{});
5359
5460 // with extension should find the .exe (case insensitive)
55 try testExec(allocator, "HeLLo.exe", "hello from exe\n");
61 try testExec(gpa, io, "HeLLo.exe", "hello from exe\n");
5662 // without extension should find the .exe (case insensitive)
57 try testExec(allocator, "heLLo", "hello from exe\n");
63 try testExec(gpa, io, "heLLo", "hello from exe\n");
5864 // with invalid cwd
59 try std.testing.expectError(error.FileNotFound, testExecWithCwd(allocator, "hello.exe", "missing_dir", ""));
65 try std.testing.expectError(error.FileNotFound, testExecWithCwd(gpa, io, "hello.exe", "missing_dir", ""));
6066
6167 // now add a .bat
62 try tmp.dir.writeFile(.{ .sub_path = "hello.bat", .data = "@echo hello from bat" });
68 try tmp.dir.writeFile(io, .{ .sub_path = "hello.bat", .data = "@echo hello from bat" });
6369 // and a .cmd
64 try tmp.dir.writeFile(.{ .sub_path = "hello.cmd", .data = "@echo hello from cmd" });
70 try tmp.dir.writeFile(io, .{ .sub_path = "hello.cmd", .data = "@echo hello from cmd" });
6571
6672 // with extension should find the .bat (case insensitive)
67 try testExec(allocator, "heLLo.bat", "hello from bat\r\n");
73 try testExec(gpa, io, "heLLo.bat", "hello from bat\r\n");
6874 // with extension should find the .cmd (case insensitive)
69 try testExec(allocator, "heLLo.cmd", "hello from cmd\r\n");
75 try testExec(gpa, io, "heLLo.cmd", "hello from cmd\r\n");
7076 // without extension should find the .exe (since its first in PATHEXT)
71 try testExec(allocator, "heLLo", "hello from exe\n");
77 try testExec(gpa, io, "heLLo", "hello from exe\n");
7278
7379 // now rename the exe to not have an extension
74 try renameExe(tmp.dir, "hello.exe", "hello");
80 try renameExe(tmp.dir, io, "hello.exe", "hello");
7581
7682 // with extension should now fail
77 try testExecError(error.FileNotFound, allocator, "hello.exe");
83 try testExecError(error.FileNotFound, gpa, io, "hello.exe");
7884 // without extension should succeed (case insensitive)
79 try testExec(allocator, "heLLo", "hello from exe\n");
85 try testExec(gpa, io, "heLLo", "hello from exe\n");
8086
81 try tmp.dir.makeDir("something");
82 try renameExe(tmp.dir, "hello", "something/hello.exe");
87 try tmp.dir.createDir(io, "something", .default_dir);
88 try renameExe(tmp.dir, io, "hello", "something/hello.exe");
8389
84 const relative_path_no_ext = try std.fs.path.join(allocator, &.{ tmp_relative_path, "something/hello" });
85 defer allocator.free(relative_path_no_ext);
90 const relative_path_no_ext = try std.fs.path.join(gpa, &.{ tmp_relative_path, "something/hello" });
91 defer gpa.free(relative_path_no_ext);
8692
8793 // Giving a full relative path to something/hello should work
88 try testExec(allocator, relative_path_no_ext, "hello from exe\n");
94 try testExec(gpa, io, relative_path_no_ext, "hello from exe\n");
8995 // But commands with path separators get excluded from PATH searching, so this will fail
90 try testExecError(error.FileNotFound, allocator, "something/hello");
96 try testExecError(error.FileNotFound, gpa, io, "something/hello");
9197
9298 // Now that .BAT is the first PATHEXT that should be found, this should succeed
93 try testExec(allocator, "heLLo", "hello from bat\r\n");
99 try testExec(gpa, io, "heLLo", "hello from bat\r\n");
94100
95101 // Add a hello.exe that is not a valid executable
96 try tmp.dir.writeFile(.{ .sub_path = "hello.exe", .data = "invalid" });
102 try tmp.dir.writeFile(io, .{ .sub_path = "hello.exe", .data = "invalid" });
97103
98104 // Trying to execute it with extension will give InvalidExe. This is a special
99105 // case for .EXE extensions, where if they ever try to get executed but they are
100106 // invalid, that gets treated as a fatal error wherever they are found and InvalidExe
101107 // is returned immediately.
102 try testExecError(error.InvalidExe, allocator, "hello.exe");
108 try testExecError(error.InvalidExe, gpa, io, "hello.exe");
103109 // Same thing applies to the command with no extension--even though there is a
104110 // hello.bat that could be executed, it should stop after it tries executing
105111 // hello.exe and getting InvalidExe.
106 try testExecError(error.InvalidExe, allocator, "hello");
112 try testExecError(error.InvalidExe, gpa, io, "hello");
107113
108114 // If we now rename hello.exe to have no extension, it will behave differently
109 try renameExe(tmp.dir, "hello.exe", "hello");
115 try renameExe(tmp.dir, io, "hello.exe", "hello");
110116
111117 // Now, trying to execute it without an extension should treat InvalidExe as recoverable
112118 // and skip over it and find hello.bat and execute that
113 try testExec(allocator, "hello", "hello from bat\r\n");
119 try testExec(gpa, io, "hello", "hello from bat\r\n");
114120
115121 // If we rename the invalid exe to something else
116 try renameExe(tmp.dir, "hello", "goodbye");
122 try renameExe(tmp.dir, io, "hello", "goodbye");
117123 // Then we should now get FileNotFound when trying to execute 'goodbye',
118124 // since that is what the original error will be after searching for 'goodbye'
119125 // in the cwd. It will try to execute 'goodbye' from the PATH but the InvalidExe error
120126 // should be ignored in this case.
121 try testExecError(error.FileNotFound, allocator, "goodbye");
127 try testExecError(error.FileNotFound, gpa, io, "goodbye");
122128
123129 // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir
124 try tmp.dir.setAsCwd();
125 defer tmp.parent_dir.setAsCwd() catch {};
126 const something_subdir_abs_path = try std.mem.concatWithSentinel(allocator, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0);
127 defer allocator.free(something_subdir_abs_path);
130 try std.process.setCurrentDir(io, tmp.dir);
131 defer std.process.setCurrentDir(io, tmp.parent_dir) catch {};
132 const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0);
133 defer gpa.free(something_subdir_abs_path);
128134
129135 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
130136 utf16Literal("PATH"),
......@@ -133,37 +139,37 @@ pub fn main() anyerror!void {
133139
134140 // Now trying to execute goodbye should give error.InvalidExe since it's the original
135141 // error that we got when trying within the cwd
136 try testExecError(error.InvalidExe, allocator, "goodbye");
142 try testExecError(error.InvalidExe, gpa, io, "goodbye");
137143
138144 // hello should still find the .bat
139 try testExec(allocator, "hello", "hello from bat\r\n");
145 try testExec(gpa, io, "hello", "hello from bat\r\n");
140146
141147 // If we rename something/hello.exe to something/goodbye.exe
142 try renameExe(tmp.dir, "something/hello.exe", "something/goodbye.exe");
148 try renameExe(tmp.dir, io, "something/hello.exe", "something/goodbye.exe");
143149 // And try to execute goodbye, then the one in something should be found
144150 // since the one in cwd is an invalid executable
145 try testExec(allocator, "goodbye", "hello from exe\n");
151 try testExec(gpa, io, "goodbye", "hello from exe\n");
146152
147153 // If we use an absolute path to execute the invalid goodbye
148 const goodbye_abs_path = try std.mem.join(allocator, "\\", &.{ tmp_absolute_path, "goodbye" });
149 defer allocator.free(goodbye_abs_path);
154 const goodbye_abs_path = try std.mem.join(gpa, "\\", &.{ tmp_absolute_path, "goodbye" });
155 defer gpa.free(goodbye_abs_path);
150156 // then the PATH should not be searched and we should get InvalidExe
151 try testExecError(error.InvalidExe, allocator, goodbye_abs_path);
157 try testExecError(error.InvalidExe, gpa, io, goodbye_abs_path);
152158
153159 // If we try to exec but provide a cwd that is an absolute path, the PATH
154160 // should still be searched and the goodbye.exe in something should be found.
155 try testExecWithCwd(allocator, "goodbye", tmp_absolute_path, "hello from exe\n");
161 try testExecWithCwd(gpa, io, "goodbye", tmp_absolute_path, "hello from exe\n");
156162
157163 // introduce some extra path separators into the path which is dealt with inside the spawn call.
158164 const denormed_something_subdir_size = std.mem.replacementSize(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"));
159165
160 const denormed_something_subdir_abs_path = try allocator.allocSentinel(u16, denormed_something_subdir_size, 0);
161 defer allocator.free(denormed_something_subdir_abs_path);
166 const denormed_something_subdir_abs_path = try gpa.allocSentinel(u16, denormed_something_subdir_size, 0);
167 defer gpa.free(denormed_something_subdir_abs_path);
162168
163169 _ = std.mem.replace(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"), denormed_something_subdir_abs_path);
164170
165 const denormed_something_subdir_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, denormed_something_subdir_abs_path);
166 defer allocator.free(denormed_something_subdir_wtf8);
171 const denormed_something_subdir_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, denormed_something_subdir_abs_path);
172 defer gpa.free(denormed_something_subdir_wtf8);
167173
168174 // clear the path to ensure that the match comes from the cwd
169175 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
......@@ -171,21 +177,21 @@ pub fn main() anyerror!void {
171177 null,
172178 ) == windows.TRUE);
173179
174 try testExecWithCwd(allocator, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n");
180 try testExecWithCwd(gpa, io, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n");
175181
176182 // normalization should also work if the non-normalized path is found in the PATH var.
177183 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
178184 utf16Literal("PATH"),
179185 denormed_something_subdir_abs_path,
180186 ) == windows.TRUE);
181 try testExec(allocator, "goodbye", "hello from exe\n");
187 try testExec(gpa, io, "goodbye", "hello from exe\n");
182188
183189 // now make sure we can launch executables "outside" of the cwd
184 var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{});
185 defer subdir_cwd.close();
190 var subdir_cwd = try tmp.dir.openDir(io, denormed_something_subdir_wtf8, .{});
191 defer subdir_cwd.close(io);
186192
187 try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe");
188 try subdir_cwd.setAsCwd();
193 try renameExe(tmp.dir, io, "something/goodbye.exe", "hello.exe");
194 try std.process.setCurrentDir(io, subdir_cwd);
189195
190196 // clear the PATH again
191197 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
......@@ -194,33 +200,32 @@ pub fn main() anyerror!void {
194200 ) == windows.TRUE);
195201
196202 // while we're at it make sure non-windows separators work fine
197 try testExec(allocator, "../hello", "hello from exe\n");
203 try testExec(gpa, io, "../hello", "hello from exe\n");
198204}
199205
200fn testExecError(err: anyerror, allocator: std.mem.Allocator, command: []const u8) !void {
201 return std.testing.expectError(err, testExec(allocator, command, ""));
206fn testExecError(err: anyerror, gpa: Allocator, io: Io, command: []const u8) !void {
207 return std.testing.expectError(err, testExec(gpa, io, command, ""));
202208}
203209
204fn testExec(allocator: std.mem.Allocator, command: []const u8, expected_stdout: []const u8) !void {
205 return testExecWithCwd(allocator, command, null, expected_stdout);
210fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []const u8) !void {
211 return testExecWithCwd(gpa, io, command, null, expected_stdout);
206212}
207213
208fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void {
209 const result = try std.process.Child.run(.{
210 .allocator = allocator,
214fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void {
215 const result = try std.process.Child.run(gpa, io, .{
211216 .argv = &[_][]const u8{command},
212217 .cwd = cwd,
213218 });
214 defer allocator.free(result.stdout);
215 defer allocator.free(result.stderr);
219 defer gpa.free(result.stdout);
220 defer gpa.free(result.stderr);
216221
217222 try std.testing.expectEqualStrings("", result.stderr);
218223 try std.testing.expectEqualStrings(expected_stdout, result.stdout);
219224}
220225
221fn renameExe(dir: std.fs.Dir, old_sub_path: []const u8, new_sub_path: []const u8) !void {
226fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void {
222227 var attempt: u5 = 0;
223 while (true) break dir.rename(old_sub_path, new_sub_path) catch |err| switch (err) {
228 while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) {
224229 error.AccessDenied => {
225230 if (attempt == 13) return error.AccessDenied;
226231 // give the kernel a chance to finish closing the executable handle
......@@ -231,3 +236,41 @@ fn renameExe(dir: std.fs.Dir, old_sub_path: []const u8, new_sub_path: []const u8
231236 else => |e| return e,
232237 };
233238}
239
240pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir {
241 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
242 std.crypto.random.bytes(&random_bytes);
243 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
244 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
245
246 const cwd = Io.Dir.cwd();
247 var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch
248 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");
249 defer cache_dir.close(io);
250 const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch
251 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir");
252 const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch
253 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
254
255 return .{
256 .dir = dir,
257 .parent_dir = parent_dir,
258 .sub_path = sub_path,
259 };
260}
261
262pub const TmpDir = struct {
263 dir: Io.Dir,
264 parent_dir: Io.Dir,
265 sub_path: [sub_path_len]u8,
266
267 const random_bytes_count = 12;
268 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
269
270 pub fn cleanup(self: *TmpDir, io: Io) void {
271 self.dir.close(io);
272 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
273 self.parent_dir.close(io);
274 self.* = undefined;
275 }
276};
test/tests.zig+65-49
......@@ -187,29 +187,30 @@ const test_targets = blk: {
187187 .link_libc = true,
188188 },
189189
190 .{
191 .target = .{
192 .cpu_arch = .aarch64,
193 .os_tag = .linux,
194 .abi = .none,
195 },
196 .use_llvm = false,
197 .use_lld = false,
198 .optimize_mode = .ReleaseFast,
199 .strip = true,
200 },
201 .{
202 .target = .{
203 .cpu_arch = .aarch64,
204 .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.neoverse_n1 },
205 .os_tag = .linux,
206 .abi = .none,
207 },
208 .use_llvm = false,
209 .use_lld = false,
210 .optimize_mode = .ReleaseFast,
211 .strip = true,
212 },
190 // Disabled due to https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9203351
191 //.{
192 // .target = .{
193 // .cpu_arch = .aarch64,
194 // .os_tag = .linux,
195 // .abi = .none,
196 // },
197 // .use_llvm = false,
198 // .use_lld = false,
199 // .optimize_mode = .ReleaseFast,
200 // .strip = true,
201 //},
202 //.{
203 // .target = .{
204 // .cpu_arch = .aarch64,
205 // .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.neoverse_n1 },
206 // .os_tag = .linux,
207 // .abi = .none,
208 // },
209 // .use_llvm = false,
210 // .use_lld = false,
211 // .optimize_mode = .ReleaseFast,
212 // .strip = true,
213 //},
213214
214215 .{
215216 .target = .{
......@@ -1204,17 +1205,18 @@ const test_targets = blk: {
12041205 },
12051206 },
12061207
1207 .{
1208 .target = .{
1209 .cpu_arch = .aarch64,
1210 .os_tag = .macos,
1211 .abi = .none,
1212 },
1213 .use_llvm = false,
1214 .use_lld = false,
1215 .optimize_mode = .ReleaseFast,
1216 .strip = true,
1217 },
1208 // Disabled due to https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9203351
1209 //.{
1210 // .target = .{
1211 // .cpu_arch = .aarch64,
1212 // .os_tag = .macos,
1213 // .abi = .none,
1214 // },
1215 // .use_llvm = false,
1216 // .use_lld = false,
1217 // .optimize_mode = .ReleaseFast,
1218 // .strip = true,
1219 //},
12181220
12191221 .{
12201222 .target = .{
......@@ -2024,6 +2026,7 @@ pub fn addLinkTests(
20242026pub fn addCliTests(b: *std.Build) *Step {
20252027 const step = b.step("test-cli", "Test the command line interface");
20262028 const s = std.fs.path.sep_str;
2029 const io = b.graph.io;
20272030
20282031 {
20292032 // Test `zig init`.
......@@ -2132,14 +2135,14 @@ pub fn addCliTests(b: *std.Build) *Step {
21322135 const tmp_path = b.makeTempPath();
21332136 const unformatted_code = " // no reason for indent";
21342137
2135 var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled");
2136 defer dir.close();
2137 dir.writeFile(.{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled");
2138 dir.writeFile(.{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled");
2139 dir.makeDir("subdir") catch @panic("unhandled");
2140 var subdir = dir.openDir("subdir", .{}) catch @panic("unhandled");
2141 defer subdir.close();
2142 subdir.writeFile(.{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled");
2138 var dir = std.Io.Dir.cwd().openDir(io, tmp_path, .{}) catch @panic("unhandled");
2139 defer dir.close(io);
2140 dir.writeFile(io, .{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled");
2141 dir.writeFile(io, .{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled");
2142 dir.createDir(io, "subdir", .default_dir) catch @panic("unhandled");
2143 var subdir = dir.openDir(io, "subdir", .{}) catch @panic("unhandled");
2144 defer subdir.close(io);
2145 subdir.writeFile(io, .{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled");
21432146
21442147 // Test zig fmt affecting only the appropriate files.
21452148 const run1 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "fmt1.zig" });
......@@ -2629,11 +2632,12 @@ pub fn addCases(
26292632) !void {
26302633 const arena = b.allocator;
26312634 const gpa = b.allocator;
2635 const io = b.graph.io;
26322636
2633 var cases = @import("src/Cases.zig").init(gpa, arena);
2637 var cases = @import("src/Cases.zig").init(gpa, arena, io);
26342638
2635 var dir = try b.build_root.handle.openDir("test/cases", .{ .iterate = true });
2636 defer dir.close();
2639 var dir = try b.build_root.handle.openDir(io, "test/cases", .{ .iterate = true });
2640 defer dir.close(io);
26372641
26382642 cases.addFromDir(dir, b);
26392643 try @import("cases.zig").addCases(&cases, build_options, b);
......@@ -2678,7 +2682,9 @@ pub fn addDebuggerTests(b: *std.Build, options: DebuggerContext.Options) ?*Step
26782682 return step;
26792683}
26802684
2681pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {
2685pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []const []const u8) !void {
2686 const io = b.graph.io;
2687
26822688 const incr_check = b.addExecutable(.{
26832689 .name = "incr-check",
26842690 .root_module = b.createModule(.{
......@@ -2688,12 +2694,17 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {
26882694 }),
26892695 });
26902696
2691 var dir = try b.build_root.handle.openDir("test/incremental", .{ .iterate = true });
2692 defer dir.close();
2697 var dir = try b.build_root.handle.openDir(io, "test/incremental", .{ .iterate = true });
2698 defer dir.close(io);
26932699
26942700 var it = try dir.walk(b.graph.arena);
2695 while (try it.next()) |entry| {
2701 while (try it.next(io)) |entry| {
26962702 if (entry.kind != .file) continue;
2703 if (std.mem.endsWith(u8, entry.basename, ".swp")) continue;
2704
2705 for (test_filters) |test_filter| {
2706 if (std.mem.indexOf(u8, entry.path, test_filter)) |_| break;
2707 } else if (test_filters.len > 0) continue;
26972708
26982709 const run = b.addRunArtifact(incr_check);
26992710 run.setName(b.fmt("incr-check '{s}'", .{entry.basename}));
......@@ -2702,6 +2713,11 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {
27022713 run.addFileArg(b.path("test/incremental/").path(b, entry.path));
27032714 run.addArgs(&.{ "--zig-lib-dir", b.fmt("{f}", .{b.graph.zig_lib_directory}) });
27042715
2716 if (b.enable_qemu) run.addArg("-fqemu");
2717 if (b.enable_wine) run.addArg("-fwine");
2718 if (b.enable_wasmtime) run.addArg("-fwasmtime");
2719 if (b.enable_darling) run.addArg("-fdarling");
2720
27052721 run.addCheck(.{ .expect_term = .{ .Exited = 0 } });
27062722
27072723 test_step.dependOn(&run.step);
tools/docgen.zig+18-16
......@@ -1,6 +1,8 @@
1const std = @import("std");
21const builtin = @import("builtin");
3const fs = std.fs;
2
3const std = @import("std");
4const Io = std.Io;
5const Dir = std.Io.Dir;
46const process = std.process;
57const Progress = std.Progress;
68const print = std.debug.print;
......@@ -8,7 +10,6 @@ const mem = std.mem;
810const testing = std.testing;
911const Allocator = std.mem.Allocator;
1012const ArrayList = std.ArrayList;
11const getExternalExecutor = std.zig.system.getExternalExecutor;
1213const fatal = std.process.fatal;
1314const Writer = std.Io.Writer;
1415
......@@ -38,7 +39,7 @@ pub fn main() !void {
3839
3940 const gpa = arena;
4041
41 var threaded: std.Io.Threaded = .init(gpa);
42 var threaded: std.Io.Threaded = .init(gpa, .{});
4243 defer threaded.deinit();
4344 const io = threaded.io();
4445
......@@ -49,7 +50,7 @@ pub fn main() !void {
4950 while (args_it.next()) |arg| {
5051 if (mem.startsWith(u8, arg, "-")) {
5152 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
52 try fs.File.stdout().writeAll(usage);
53 try Io.File.stdout().writeStreamingAll(io, usage);
5354 process.exit(0);
5455 } else if (mem.eql(u8, arg, "--code-dir")) {
5556 if (args_it.next()) |param| {
......@@ -72,16 +73,16 @@ pub fn main() !void {
7273 const output_path = opt_output orelse fatal("missing output file", .{});
7374 const code_dir_path = opt_code_dir orelse fatal("missing --code-dir argument", .{});
7475
75 var in_file = try fs.cwd().openFile(input_path, .{});
76 defer in_file.close();
76 var in_file = try Dir.cwd().openFile(io, input_path, .{});
77 defer in_file.close(io);
7778
78 var out_file = try fs.cwd().createFile(output_path, .{});
79 defer out_file.close();
79 var out_file = try Dir.cwd().createFile(io, output_path, .{});
80 defer out_file.close(io);
8081 var out_file_buffer: [4096]u8 = undefined;
81 var out_file_writer = out_file.writer(&out_file_buffer);
82 var out_file_writer = out_file.writer(io, &out_file_buffer);
8283
83 var code_dir = try fs.cwd().openDir(code_dir_path, .{});
84 defer code_dir.close();
84 var code_dir = try Dir.cwd().openDir(io, code_dir_path, .{});
85 defer code_dir.close(io);
8586
8687 var in_file_reader = in_file.reader(io, &.{});
8788 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .limited(max_doc_file_size));
......@@ -89,7 +90,7 @@ pub fn main() !void {
8990 var tokenizer = Tokenizer.init(input_path, input_file_bytes);
9091 var toc = try genToc(arena, &tokenizer);
9192
92 try genHtml(arena, &tokenizer, &toc, code_dir, &out_file_writer.interface);
93 try genHtml(arena, io, &tokenizer, &toc, code_dir, &out_file_writer.interface);
9394 try out_file_writer.end();
9495}
9596
......@@ -988,9 +989,10 @@ fn printShell(out: *Writer, shell_content: []const u8, escape: bool) !void {
988989
989990fn genHtml(
990991 allocator: Allocator,
992 io: Io,
991993 tokenizer: *Tokenizer,
992994 toc: *Toc,
993 code_dir: std.fs.Dir,
995 code_dir: Dir,
994996 out: *Writer,
995997) !void {
996998 for (toc.nodes) |node| {
......@@ -1042,11 +1044,11 @@ fn genHtml(
10421044 },
10431045 .Code => |code| {
10441046 const out_basename = try std.fmt.allocPrint(allocator, "{s}.out", .{
1045 fs.path.stem(code.name),
1047 Dir.path.stem(code.name),
10461048 });
10471049 defer allocator.free(out_basename);
10481050
1049 const contents = code_dir.readFileAlloc(out_basename, allocator, .limited(std.math.maxInt(u32))) catch |err| {
1051 const contents = code_dir.readFileAlloc(io, out_basename, allocator, .limited(std.math.maxInt(u32))) catch |err| {
10501052 return parseError(tokenizer, code.token, "unable to open '{s}': {t}", .{ out_basename, err });
10511053 };
10521054 defer allocator.free(contents);
tools/doctest.zig+29-34
......@@ -2,10 +2,10 @@ const builtin = @import("builtin");
22
33const std = @import("std");
44const Io = std.Io;
5const Dir = std.Io.Dir;
56const Writer = std.Io.Writer;
67const fatal = std.process.fatal;
78const mem = std.mem;
8const fs = std.fs;
99const process = std.process;
1010const Allocator = std.mem.Allocator;
1111const testing = std.testing;
......@@ -40,7 +40,7 @@ pub fn main() !void {
4040
4141 const gpa = arena;
4242
43 var threaded: std.Io.Threaded = .init(gpa);
43 var threaded: std.Io.Threaded = .init(gpa, .{});
4444 defer threaded.deinit();
4545 const io = threaded.io();
4646
......@@ -53,7 +53,7 @@ pub fn main() !void {
5353 while (args_it.next()) |arg| {
5454 if (mem.startsWith(u8, arg, "-")) {
5555 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
56 try std.fs.File.stdout().writeAll(usage);
56 try Io.File.stdout().writeStreamingAll(io, usage);
5757 process.exit(0);
5858 } else if (mem.eql(u8, arg, "-i")) {
5959 opt_input = args_it.next() orelse fatal("expected parameter after -i", .{});
......@@ -78,37 +78,37 @@ pub fn main() !void {
7878 const zig_path = opt_zig orelse fatal("missing zig compiler path (--zig)", .{});
7979 const cache_root = opt_cache_root orelse fatal("missing cache root path (--cache-root)", .{});
8080
81 const source_bytes = try fs.cwd().readFileAlloc(input_path, arena, .limited(std.math.maxInt(u32)));
81 const source_bytes = try Dir.cwd().readFileAlloc(io, input_path, arena, .limited(std.math.maxInt(u32)));
8282 const code = try parseManifest(arena, source_bytes);
8383 const source = stripManifest(source_bytes);
8484
8585 const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{
8686 cache_root, std.crypto.random.int(u64),
8787 });
88 fs.cwd().makePath(tmp_dir_path) catch |err|
89 fatal("unable to create tmp dir '{s}': {s}", .{ tmp_dir_path, @errorName(err) });
90 defer fs.cwd().deleteTree(tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {s}", .{
91 tmp_dir_path, @errorName(err),
88 Dir.cwd().createDirPath(io, tmp_dir_path) catch |err|
89 fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err });
90 defer Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{
91 tmp_dir_path, err,
9292 });
9393
94 var out_file = try fs.cwd().createFile(output_path, .{});
95 defer out_file.close();
94 var out_file = try Dir.cwd().createFile(io, output_path, .{});
95 defer out_file.close(io);
9696 var out_file_buffer: [4096]u8 = undefined;
97 var out_file_writer = out_file.writer(&out_file_buffer);
97 var out_file_writer = out_file.writer(io, &out_file_buffer);
9898
9999 const out = &out_file_writer.interface;
100100
101 try printSourceBlock(arena, out, source, fs.path.basename(input_path));
101 try printSourceBlock(arena, out, source, Dir.path.basename(input_path));
102102 try printOutput(
103103 arena,
104104 io,
105105 out,
106106 code,
107107 tmp_dir_path,
108 try std.fs.path.relative(arena, tmp_dir_path, zig_path),
109 try std.fs.path.relative(arena, tmp_dir_path, input_path),
108 try Dir.path.relative(arena, tmp_dir_path, zig_path),
109 try Dir.path.relative(arena, tmp_dir_path, input_path),
110110 if (opt_zig_lib_dir) |zig_lib_dir|
111 try std.fs.path.relative(arena, tmp_dir_path, zig_lib_dir)
111 try Dir.path.relative(arena, tmp_dir_path, zig_lib_dir)
112112 else
113113 null,
114114 );
......@@ -141,7 +141,7 @@ fn printOutput(
141141 defer shell_buffer.deinit();
142142 const shell_out = &shell_buffer.writer;
143143
144 const code_name = std.fs.path.stem(input_path);
144 const code_name = Dir.path.stem(input_path);
145145
146146 switch (code.id) {
147147 .exe => |expected_outcome| code_block: {
......@@ -201,8 +201,7 @@ fn printOutput(
201201 try shell_out.print("\n", .{});
202202
203203 if (expected_outcome == .build_fail) {
204 const result = try process.Child.run(.{
205 .allocator = arena,
204 const result = try process.Child.run(arena, io, .{
206205 .argv = build_args.items,
207206 .cwd = tmp_dir_path,
208207 .env_map = &env_map,
......@@ -227,7 +226,7 @@ fn printOutput(
227226 try shell_out.writeAll(colored_stderr);
228227 break :code_block;
229228 }
230 const exec_result = run(arena, &env_map, tmp_dir_path, build_args.items) catch
229 const exec_result = run(arena, io, &env_map, tmp_dir_path, build_args.items) catch
231230 fatal("example failed to compile", .{});
232231
233232 if (code.verbose_cimport) {
......@@ -258,8 +257,7 @@ fn printOutput(
258257 var exited_with_signal = false;
259258
260259 const result = if (expected_outcome == .fail) blk: {
261 const result = try process.Child.run(.{
262 .allocator = arena,
260 const result = try process.Child.run(arena, io, .{
263261 .argv = run_args,
264262 .env_map = &env_map,
265263 .cwd = tmp_dir_path,
......@@ -278,7 +276,7 @@ fn printOutput(
278276 }
279277 break :blk result;
280278 } else blk: {
281 break :blk run(arena, &env_map, tmp_dir_path, run_args) catch
279 break :blk run(arena, io, &env_map, tmp_dir_path, run_args) catch
282280 fatal("example crashed", .{});
283281 };
284282
......@@ -327,7 +325,7 @@ fn printOutput(
327325 .arch_os_abi = triple,
328326 });
329327 const target = try std.zig.system.resolveTargetQuery(io, target_query);
330 switch (getExternalExecutor(&host, &target, .{
328 switch (getExternalExecutor(io, &host, &target, .{
331329 .link_libc = code.link_libc,
332330 })) {
333331 .native => {},
......@@ -347,7 +345,7 @@ fn printOutput(
347345 }
348346 }
349347
350 const result = run(arena, &env_map, tmp_dir_path, test_args.items) catch
348 const result = run(arena, io, &env_map, tmp_dir_path, test_args.items) catch
351349 fatal("test failed", .{});
352350 const escaped_stderr = try escapeHtml(arena, result.stderr);
353351 const escaped_stdout = try escapeHtml(arena, result.stdout);
......@@ -378,8 +376,7 @@ fn printOutput(
378376 try test_args.append("-lc");
379377 try shell_out.print("-lc ", .{});
380378 }
381 const result = try process.Child.run(.{
382 .allocator = arena,
379 const result = try process.Child.run(arena, io, .{
383380 .argv = test_args.items,
384381 .env_map = &env_map,
385382 .cwd = tmp_dir_path,
......@@ -435,8 +432,7 @@ fn printOutput(
435432 },
436433 }
437434
438 const result = try process.Child.run(.{
439 .allocator = arena,
435 const result = try process.Child.run(arena, io, .{
440436 .argv = test_args.items,
441437 .env_map = &env_map,
442438 .cwd = tmp_dir_path,
......@@ -512,8 +508,7 @@ fn printOutput(
512508 }
513509
514510 if (maybe_error_match) |error_match| {
515 const result = try process.Child.run(.{
516 .allocator = arena,
511 const result = try process.Child.run(arena, io, .{
517512 .argv = build_args.items,
518513 .env_map = &env_map,
519514 .cwd = tmp_dir_path,
......@@ -541,7 +536,7 @@ fn printOutput(
541536 const colored_stderr = try termColor(arena, escaped_stderr);
542537 try shell_out.print("\n{s} ", .{colored_stderr});
543538 } else {
544 _ = run(arena, &env_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{});
539 _ = run(arena, io, &env_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{});
545540 }
546541 try shell_out.writeAll("\n");
547542 },
......@@ -600,7 +595,7 @@ fn printOutput(
600595 try test_args.append(option);
601596 try shell_out.print("{s} ", .{option});
602597 }
603 const result = run(arena, &env_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{});
598 const result = run(arena, io, &env_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{});
604599 const escaped_stderr = try escapeHtml(arena, result.stderr);
605600 const escaped_stdout = try escapeHtml(arena, result.stdout);
606601 try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });
......@@ -1132,12 +1127,12 @@ fn in(slice: []const u8, number: u8) bool {
11321127
11331128fn run(
11341129 allocator: Allocator,
1130 io: Io,
11351131 env_map: *process.EnvMap,
11361132 cwd: []const u8,
11371133 args: []const []const u8,
11381134) !process.Child.RunResult {
1139 const result = try process.Child.run(.{
1140 .allocator = allocator,
1135 const result = try process.Child.run(allocator, io, .{
11411136 .argv = args,
11421137 .env_map = env_map,
11431138 .cwd = cwd,
tools/dump-cov.zig+7-5
......@@ -2,6 +2,7 @@
22//! including file:line:column information for each PC.
33
44const std = @import("std");
5const Io = std.Io;
56const fatal = std.process.fatal;
67const Path = std.Build.Cache.Path;
78const assert = std.debug.assert;
......@@ -16,7 +17,7 @@ pub fn main() !void {
1617 defer arena_instance.deinit();
1718 const arena = arena_instance.allocator();
1819
19 var threaded: std.Io.Threaded = .init(gpa);
20 var threaded: Io.Threaded = .init(gpa, .{});
2021 defer threaded.deinit();
2122 const io = threaded.io();
2223
......@@ -51,12 +52,13 @@ pub fn main() !void {
5152 var coverage: std.debug.Coverage = .init;
5253 defer coverage.deinit(gpa);
5354
54 var debug_info = std.debug.Info.load(gpa, exe_path, &coverage, target.ofmt, target.cpu.arch) catch |err| {
55 fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) });
55 var debug_info = std.debug.Info.load(gpa, io, exe_path, &coverage, target.ofmt, target.cpu.arch) catch |err| {
56 fatal("failed to load debug info for {f}: {t}", .{ exe_path, err });
5657 };
5758 defer debug_info.deinit(gpa);
5859
5960 const cov_bytes = cov_path.root_dir.handle.readFileAllocOptions(
61 io,
6062 cov_path.sub_path,
6163 arena,
6264 .limited(1 << 30),
......@@ -67,7 +69,7 @@ pub fn main() !void {
6769 };
6870
6971 var stdout_buffer: [4000]u8 = undefined;
70 var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
72 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
7173 const stdout = &stdout_writer.interface;
7274
7375 const header: *SeenPcsHeader = @ptrCast(cov_bytes);
......@@ -83,7 +85,7 @@ pub fn main() !void {
8385 std.mem.sortUnstable(usize, sorted_pcs, {}, std.sort.asc(usize));
8486
8587 const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, sorted_pcs.len);
86 try debug_info.resolveAddresses(gpa, sorted_pcs, source_locations);
88 try debug_info.resolveAddresses(gpa, io, sorted_pcs, source_locations);
8789
8890 const seen_pcs = header.seenBits();
8991
tools/fetch_them_macos_headers.zig+28-33
......@@ -1,10 +1,9 @@
11const std = @import("std");
22const Io = std.Io;
3const fs = std.fs;
3const Dir = std.Io.Dir;
44const mem = std.mem;
55const process = std.process;
66const assert = std.debug.assert;
7const tmpDir = std.testing.tmpDir;
87const fatal = std.process.fatal;
98const info = std.log.info;
109
......@@ -86,19 +85,19 @@ pub fn main() anyerror!void {
8685 } else try argv.append(arg);
8786 }
8887
89 var threaded: Io.Threaded = .init(gpa);
88 var threaded: Io.Threaded = .init(gpa, .{});
9089 defer threaded.deinit();
9190 const io = threaded.io();
9291
9392 const sysroot_path = sysroot orelse blk: {
9493 const target = try std.zig.system.resolveTargetQuery(io, .{});
95 break :blk std.zig.system.darwin.getSdk(allocator, &target) orelse
94 break :blk std.zig.system.darwin.getSdk(allocator, io, &target) orelse
9695 fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{});
9796 };
9897
99 var sdk_dir = try std.fs.cwd().openDir(sysroot_path, .{});
100 defer sdk_dir.close();
101 const sdk_info = try sdk_dir.readFileAlloc("SDKSettings.json", allocator, .limited(std.math.maxInt(u32)));
98 var sdk_dir = try Dir.cwd().openDir(io, sysroot_path, .{});
99 defer sdk_dir.close(io);
100 const sdk_info = try sdk_dir.readFileAlloc(io, "SDKSettings.json", allocator, .limited(std.math.maxInt(u32)));
102101
103102 const parsed_json = try std.json.parseFromSlice(struct {
104103 DefaultProperties: struct { MACOSX_DEPLOYMENT_TARGET: []const u8 },
......@@ -111,15 +110,14 @@ pub fn main() anyerror!void {
111110 const os_ver: OsVer = @enumFromInt(version.major);
112111 info("found SDK deployment target macOS {f} aka '{t}'", .{ version, os_ver });
113112
114 var tmp = tmpDir(.{});
115 defer tmp.cleanup();
113 const tmp_dir: Io.Dir = .cwd();
116114
117115 for (&[_]Arch{ .aarch64, .x86_64 }) |arch| {
118116 const target: Target = .{
119117 .arch = arch,
120118 .os_ver = os_ver,
121119 };
122 try fetchTarget(allocator, io, argv.items, sysroot_path, target, version, tmp);
120 try fetchTarget(allocator, io, argv.items, sysroot_path, target, version, tmp_dir);
123121 }
124122}
125123
......@@ -130,13 +128,13 @@ fn fetchTarget(
130128 sysroot: []const u8,
131129 target: Target,
132130 ver: Version,
133 tmp: std.testing.TmpDir,
131 tmp_dir: Io.Dir,
134132) !void {
135133 const tmp_filename = "macos-headers";
136134 const headers_list_filename = "macos-headers.o.d";
137 const tmp_path = try tmp.dir.realpathAlloc(arena, ".");
138 const tmp_file_path = try fs.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename });
139 const headers_list_path = try fs.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename });
135 const tmp_path = try tmp_dir.realPathFileAlloc(io, ".", arena);
136 const tmp_file_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename });
137 const headers_list_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename });
140138
141139 const macos_version = try std.fmt.allocPrint(arena, "-mmacosx-version-min={d}.{d}", .{
142140 ver.major,
......@@ -166,20 +164,17 @@ fn fetchTarget(
166164 });
167165 try cc_argv.appendSlice(args);
168166
169 const res = try std.process.Child.run(.{
170 .allocator = arena,
171 .argv = cc_argv.items,
172 });
167 const res = try std.process.Child.run(arena, io, .{ .argv = cc_argv.items });
173168
174169 if (res.stderr.len != 0) {
175170 std.log.err("{s}", .{res.stderr});
176171 }
177172
178173 // Read in the contents of `macos-headers.o.d`
179 const headers_list_file = try tmp.dir.openFile(headers_list_filename, .{});
180 defer headers_list_file.close();
174 const headers_list_file = try tmp_dir.openFile(io, headers_list_filename, .{});
175 defer headers_list_file.close(io);
181176
182 var headers_dir = fs.cwd().openDir(headers_source_prefix, .{}) catch |err| switch (err) {
177 var headers_dir = Dir.cwd().openDir(io, headers_source_prefix, .{}) catch |err| switch (err) {
183178 error.FileNotFound,
184179 error.NotDir,
185180 => fatal("path '{s}' not found or not a directory. Did you accidentally delete it?", .{
......@@ -187,13 +182,13 @@ fn fetchTarget(
187182 }),
188183 else => return err,
189184 };
190 defer headers_dir.close();
185 defer headers_dir.close(io);
191186
192187 const dest_path = try target.fullName(arena);
193 try headers_dir.deleteTree(dest_path);
188 try headers_dir.deleteTree(io, dest_path);
194189
195 var dest_dir = try headers_dir.makeOpenPath(dest_path, .{});
196 var dirs = std.StringHashMap(fs.Dir).init(arena);
190 var dest_dir = try headers_dir.createDirPathOpen(io, dest_path, .{});
191 var dirs = std.StringHashMap(Dir).init(arena);
197192 try dirs.putNoClobber(".", dest_dir);
198193
199194 var headers_list_file_reader = headers_list_file.reader(io, &.{});
......@@ -206,25 +201,25 @@ fn fetchTarget(
206201 if (mem.lastIndexOf(u8, line, prefix[0..])) |idx| {
207202 const out_rel_path = line[idx + prefix.len + 1 ..];
208203 const out_rel_path_stripped = mem.trim(u8, out_rel_path, " \\");
209 const dirname = fs.path.dirname(out_rel_path_stripped) orelse ".";
204 const dirname = Dir.path.dirname(out_rel_path_stripped) orelse ".";
210205 const maybe_dir = try dirs.getOrPut(dirname);
211206 if (!maybe_dir.found_existing) {
212 maybe_dir.value_ptr.* = try dest_dir.makeOpenPath(dirname, .{});
207 maybe_dir.value_ptr.* = try dest_dir.createDirPathOpen(io, dirname, .{});
213208 }
214 const basename = fs.path.basename(out_rel_path_stripped);
209 const basename = Dir.path.basename(out_rel_path_stripped);
215210
216211 const line_stripped = mem.trim(u8, line, " \\");
217 const abs_dirname = fs.path.dirname(line_stripped).?;
218 var orig_subdir = try fs.cwd().openDir(abs_dirname, .{});
219 defer orig_subdir.close();
212 const abs_dirname = Dir.path.dirname(line_stripped).?;
213 var orig_subdir = try Dir.cwd().openDir(io, abs_dirname, .{});
214 defer orig_subdir.close(io);
220215
221 try orig_subdir.copyFile(basename, maybe_dir.value_ptr.*, basename, .{});
216 try orig_subdir.copyFile(basename, maybe_dir.value_ptr.*, basename, io, .{});
222217 }
223218 }
224219
225220 var dir_it = dirs.iterator();
226221 while (dir_it.next()) |entry| {
227 entry.value_ptr.close();
222 entry.value_ptr.close(io);
228223 }
229224}
230225
tools/gen_macos_headers_c.zig+19-13
......@@ -1,8 +1,9 @@
11const std = @import("std");
2const Io = std.Io;
3const Dir = std.Io.Dir;
24const assert = std.debug.assert;
35const info = std.log.info;
46const fatal = std.process.fatal;
5
67const Allocator = std.mem.Allocator;
78
89var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
......@@ -20,6 +21,10 @@ pub fn main() anyerror!void {
2021 defer arena_allocator.deinit();
2122 const arena = arena_allocator.allocator();
2223
24 var threaded: Io.Threaded = .init(gpa, .{});
25 defer threaded.deinit();
26 const io = threaded.io();
27
2328 const args = try std.process.argsAlloc(arena);
2429 if (args.len == 1) fatal("no command or option specified", .{});
2530
......@@ -33,10 +38,10 @@ pub fn main() anyerror!void {
3338
3439 if (positionals.items.len != 1) fatal("expected one positional argument: [dir]", .{});
3540
36 var dir = try std.fs.cwd().openDir(positionals.items[0], .{ .follow_symlinks = false });
37 defer dir.close();
41 var dir = try Io.Dir.cwd().openDir(io, positionals.items[0], .{ .follow_symlinks = false });
42 defer dir.close(io);
3843 var paths = std.array_list.Managed([]const u8).init(arena);
39 try findHeaders(arena, dir, "", &paths);
44 try findHeaders(arena, io, dir, "", &paths);
4045
4146 const SortFn = struct {
4247 pub fn lessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool {
......@@ -48,7 +53,7 @@ pub fn main() anyerror!void {
4853 std.mem.sort([]const u8, paths.items, {}, SortFn.lessThan);
4954
5055 var buffer: [2000]u8 = undefined;
51 var stdout_writer = std.fs.File.stdout().writerStreaming(&buffer);
56 var stdout_writer = Io.File.stdout().writerStreaming(io, &buffer);
5257 const w = &stdout_writer.interface;
5358 try w.writeAll("#define _XOPEN_SOURCE\n");
5459 for (paths.items) |path| {
......@@ -64,23 +69,24 @@ pub fn main() anyerror!void {
6469
6570fn findHeaders(
6671 arena: Allocator,
67 dir: std.fs.Dir,
72 io: Io,
73 dir: Dir,
6874 prefix: []const u8,
6975 paths: *std.array_list.Managed([]const u8),
7076) anyerror!void {
7177 var it = dir.iterate();
72 while (try it.next()) |entry| {
78 while (try it.next(io)) |entry| {
7379 switch (entry.kind) {
7480 .directory => {
75 const path = try std.fs.path.join(arena, &.{ prefix, entry.name });
76 var subdir = try dir.openDir(entry.name, .{ .follow_symlinks = false });
77 defer subdir.close();
78 try findHeaders(arena, subdir, path, paths);
81 const path = try Io.Dir.path.join(arena, &.{ prefix, entry.name });
82 var subdir = try dir.openDir(io, entry.name, .{ .follow_symlinks = false });
83 defer subdir.close(io);
84 try findHeaders(arena, io, subdir, path, paths);
7985 },
8086 .file, .sym_link => {
81 const ext = std.fs.path.extension(entry.name);
87 const ext = Io.Dir.path.extension(entry.name);
8288 if (!std.mem.eql(u8, ext, ".h")) continue;
83 const path = try std.fs.path.join(arena, &.{ prefix, entry.name });
89 const path = try Io.Dir.path.join(arena, &.{ prefix, entry.name });
8490 try paths.append(path);
8591 },
8692 else => {},
tools/gen_outline_atomics.zig+6-1
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const Allocator = std.mem.Allocator;
34
45const AtomicOp = enum {
......@@ -15,10 +16,14 @@ pub fn main() !void {
1516 defer arena_instance.deinit();
1617 const arena = arena_instance.allocator();
1718
19 var threaded: std.Io.Threaded = .init(arena, .{});
20 defer threaded.deinit();
21 const io = threaded.io();
22
1823 //const args = try std.process.argsAlloc(arena);
1924
2025 var stdout_buffer: [2000]u8 = undefined;
21 var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
26 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
2227 const w = &stdout_writer.interface;
2328
2429 try w.writeAll(
tools/gen_spirv_spec.zig+23-16
......@@ -1,5 +1,7 @@
11const std = @import("std");
2const Io = std.Io;
23const Allocator = std.mem.Allocator;
4
35const g = @import("spirv/grammar.zig");
46const CoreRegistry = g.CoreRegistry;
57const ExtensionRegistry = g.ExtensionRegistry;
......@@ -63,24 +65,28 @@ pub fn main() !void {
6365 usageAndExit(args[0], 1);
6466 }
6567
66 const json_path = try std.fs.path.join(allocator, &.{ args[1], "include/spirv/unified1/" });
67 const dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true });
68 var threaded: std.Io.Threaded = .init(allocator, .{});
69 defer threaded.deinit();
70 const io = threaded.io();
71
72 const json_path = try Io.Dir.path.join(allocator, &.{ args[1], "include/spirv/unified1/" });
73 const dir = try Io.Dir.cwd().openDir(io, json_path, .{ .iterate = true });
6874
69 const core_spec = try readRegistry(CoreRegistry, dir, "spirv.core.grammar.json");
75 const core_spec = try readRegistry(io, CoreRegistry, dir, "spirv.core.grammar.json");
7076 std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt);
7177
7278 var exts = std.array_list.Managed(Extension).init(allocator);
7379
7480 var it = dir.iterate();
75 while (try it.next()) |entry| {
81 while (try it.next(io)) |entry| {
7682 if (entry.kind != .file) {
7783 continue;
7884 }
7985
80 try readExtRegistry(&exts, dir, entry.name);
86 try readExtRegistry(io, &exts, dir, entry.name);
8187 }
8288
83 try readExtRegistry(&exts, std.fs.cwd(), args[2]);
89 try readExtRegistry(io, &exts, Io.Dir.cwd(), args[2]);
8490
8591 var allocating: std.Io.Writer.Allocating = .init(allocator);
8692 defer allocating.deinit();
......@@ -91,7 +97,7 @@ pub fn main() !void {
9197 var tree = try std.zig.Ast.parse(allocator, output, .zig);
9298
9399 if (tree.errors.len != 0) {
94 try std.zig.printAstErrorsToStderr(allocator, tree, "", .auto);
100 try std.zig.printAstErrorsToStderr(allocator, io, tree, "", .auto);
95101 return;
96102 }
97103
......@@ -103,22 +109,22 @@ pub fn main() !void {
103109 try wip_errors.addZirErrorMessages(zir, tree, output, "");
104110 var error_bundle = try wip_errors.toOwnedBundle("");
105111 defer error_bundle.deinit(allocator);
106 error_bundle.renderToStdErr(.{}, .auto);
112 try error_bundle.renderToStderr(io, .{}, .auto);
107113 }
108114
109115 const formatted_output = try tree.renderAlloc(allocator);
110 _ = try std.fs.File.stdout().write(formatted_output);
116 try Io.File.stdout().writeStreamingAll(io, formatted_output);
111117}
112118
113fn readExtRegistry(exts: *std.array_list.Managed(Extension), dir: std.fs.Dir, sub_path: []const u8) !void {
114 const filename = std.fs.path.basename(sub_path);
119fn readExtRegistry(io: Io, exts: *std.array_list.Managed(Extension), dir: Io.Dir, sub_path: []const u8) !void {
120 const filename = Io.Dir.path.basename(sub_path);
115121 if (!std.mem.startsWith(u8, filename, "extinst.")) {
116122 return;
117123 }
118124
119125 std.debug.assert(std.mem.endsWith(u8, filename, ".grammar.json"));
120126 const name = filename["extinst.".len .. filename.len - ".grammar.json".len];
121 const spec = try readRegistry(ExtensionRegistry, dir, sub_path);
127 const spec = try readRegistry(io, ExtensionRegistry, dir, sub_path);
122128
123129 const set_name = set_names.get(name) orelse {
124130 std.log.info("ignored instruction set '{s}'", .{name});
......@@ -134,8 +140,8 @@ fn readExtRegistry(exts: *std.array_list.Managed(Extension), dir: std.fs.Dir, su
134140 });
135141}
136142
137fn readRegistry(comptime RegistryType: type, dir: std.fs.Dir, path: []const u8) !RegistryType {
138 const spec = try dir.readFileAlloc(path, allocator, .unlimited);
143fn readRegistry(io: Io, comptime RegistryType: type, dir: Io.Dir, path: []const u8) !RegistryType {
144 const spec = try dir.readFileAlloc(io, path, allocator, .unlimited);
139145 // Required for json parsing.
140146 // TODO: ALI
141147 @setEvalBranchQuota(10000);
......@@ -930,8 +936,9 @@ fn parseHexInt(text: []const u8) !u31 {
930936}
931937
932938fn usageAndExit(arg0: []const u8, code: u8) noreturn {
933 const stderr, _ = std.debug.lockStderrWriter(&.{});
934 stderr.print(
939 const stderr = std.debug.lockStderr(&.{});
940 const w = &stderr.file_writer.interface;
941 w.print(
935942 \\Usage: {s} <SPIRV-Headers repository path> <path/to/zig/src/codegen/spirv/extinst.zig.grammar.json>
936943 \\
937944 \\Generates Zig bindings for SPIR-V specifications found in the SPIRV-Headers
tools/gen_stubs.zig+12-5
......@@ -55,12 +55,14 @@
5555// - e.g. find a common previous symbol and put it after that one
5656// - they definitely need to go into the correct section
5757
58const builtin = @import("builtin");
59const native_endian = builtin.cpu.arch.endian();
60
5861const std = @import("std");
59const builtin = std.builtin;
62const Io = std.Io;
6063const mem = std.mem;
6164const log = std.log;
6265const elf = std.elf;
63const native_endian = @import("builtin").cpu.arch.endian();
6466
6567const Arch = enum {
6668 aarch64,
......@@ -284,10 +286,14 @@ pub fn main() !void {
284286 defer arena_instance.deinit();
285287 const arena = arena_instance.allocator();
286288
289 var threaded: std.Io.Threaded = .init(arena, .{});
290 defer threaded.deinit();
291 const io = threaded.io();
292
287293 const args = try std.process.argsAlloc(arena);
288294 const build_all_path = args[1];
289295
290 var build_all_dir = try std.fs.cwd().openDir(build_all_path, .{});
296 var build_all_dir = try Io.Dir.cwd().openDir(io, build_all_path, .{});
291297
292298 var sym_table = std.StringArrayHashMap(MultiSym).init(arena);
293299 var sections = std.StringArrayHashMap(void).init(arena);
......@@ -299,6 +305,7 @@ pub fn main() !void {
299305
300306 // Read the ELF header.
301307 const elf_bytes = build_all_dir.readFileAllocOptions(
308 io,
302309 libc_so_path,
303310 arena,
304311 .limited(100 * 1024 * 1024),
......@@ -334,7 +341,7 @@ pub fn main() !void {
334341 }
335342
336343 var stdout_buffer: [2000]u8 = undefined;
337 var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
344 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
338345 const stdout = &stdout_writer.interface;
339346 try stdout.writeAll(
340347 \\#ifdef PTR64
......@@ -539,7 +546,7 @@ pub fn main() !void {
539546 try stdout.flush();
540547}
541548
542fn parseElf(parse: Parse, comptime is_64: bool, comptime endian: builtin.Endian) !void {
549fn parseElf(parse: Parse, comptime is_64: bool, comptime endian: std.builtin.Endian) !void {
543550 const arena = parse.arena;
544551 const elf_bytes = parse.elf_bytes;
545552 const header = parse.header;
tools/generate_JSONTestSuite.zig+9-4
......@@ -1,13 +1,18 @@
11// zig run this file inside the test_parsing/ directory of this repo: https://github.com/nst/JSONTestSuite
22
33const std = @import("std");
4const Io = std.Io;
45
56pub fn main() !void {
67 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
78 var allocator = gpa.allocator();
89
10 var threaded: std.Io.Threaded = .init(allocator, .{});
11 defer threaded.deinit();
12 const io = threaded.io();
13
914 var stdout_buffer: [2000]u8 = undefined;
10 var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
15 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
1116 const output = &stdout_writer.interface;
1217 try output.writeAll(
1318 \\// This file was generated by _generate_JSONTestSuite.zig
......@@ -20,9 +25,9 @@ pub fn main() !void {
2025 );
2126
2227 var names = std.array_list.Managed([]const u8).init(allocator);
23 var cwd = try std.fs.cwd().openDir(".", .{ .iterate = true });
28 var cwd = try Io.Dir.cwd().openDir(io, ".", .{ .iterate = true });
2429 var it = cwd.iterate();
25 while (try it.next()) |entry| {
30 while (try it.next(io)) |entry| {
2631 try names.append(try allocator.dupe(u8, entry.name));
2732 }
2833 std.mem.sort([]const u8, names.items, {}, (struct {
......@@ -32,7 +37,7 @@ pub fn main() !void {
3237 }).lessThan);
3338
3439 for (names.items) |name| {
35 const contents = try std.fs.cwd().readFileAlloc(name, allocator, .limited(250001));
40 const contents = try Io.Dir.cwd().readFileAlloc(io, name, allocator, .limited(250001));
3641 try output.writeAll("test ");
3742 try writeString(output, name);
3843 try output.writeAll(" {\n try ");
tools/generate_c_size_and_align_checks.zig+3-2
......@@ -7,6 +7,7 @@
77//! target.
88
99const std = @import("std");
10const Io = std.Io;
1011
1112fn cName(ty: std.Target.CType) []const u8 {
1213 return switch (ty) {
......@@ -39,7 +40,7 @@ pub fn main() !void {
3940 std.process.exit(1);
4041 }
4142
42 var threaded: std.Io.Threaded = .init(gpa);
43 var threaded: std.Io.Threaded = .init(gpa, .{});
4344 defer threaded.deinit();
4445 const io = threaded.io();
4546
......@@ -47,7 +48,7 @@ pub fn main() !void {
4748 const target = try std.zig.system.resolveTargetQuery(io, query);
4849
4950 var buffer: [2000]u8 = undefined;
50 var stdout_writer = std.fs.File.stdout().writerStreaming(&buffer);
51 var stdout_writer = Io.File.stdout().writerStreaming(io, &buffer);
5152 const w = &stdout_writer.interface;
5253 inline for (@typeInfo(std.Target.CType).@"enum".fields) |field| {
5354 const c_type: std.Target.CType = @enumFromInt(field.value);
tools/generate_linux_syscalls.zig+11-7
......@@ -175,21 +175,25 @@ pub fn main() !void {
175175 defer arena.deinit();
176176 const gpa = arena.allocator();
177177
178 var threaded: Io.Threaded = .init(gpa, .{});
179 defer threaded.deinit();
180 const io = threaded.io();
181
178182 const args = try std.process.argsAlloc(gpa);
179183 if (args.len < 2 or mem.eql(u8, args[1], "--help")) {
180 const w, _ = std.debug.lockStderrWriter(&.{});
181 defer std.debug.unlockStderrWriter();
184 const stderr = std.debug.lockStderr(&.{});
185 const w = &stderr.file_writer.interface;
182186 usage(w, args[0]) catch std.process.exit(2);
183187 std.process.exit(1);
184188 }
185189 const linux_path = args[1];
186190
187191 var stdout_buffer: [2048]u8 = undefined;
188 var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
192 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
189193 const stdout = &stdout_writer.interface;
190194
191 var linux_dir = try std.fs.cwd().openDir(linux_path, .{});
192 defer linux_dir.close();
195 var linux_dir = try Io.Dir.cwd().openDir(io, linux_path, .{});
196 defer linux_dir.close(io);
193197
194198 // As of 6.11, the largest table is 24195 bytes.
195199 // 32k should be enough for now.
......@@ -198,7 +202,7 @@ pub fn main() !void {
198202
199203 // Fetch the kernel version from the Makefile variables.
200204 const version = blk: {
201 const head = try linux_dir.readFile("Makefile", buf[0..128]);
205 const head = try linux_dir.readFile(io, "Makefile", buf[0..128]);
202206 var lines = mem.tokenizeScalar(u8, head, '\n');
203207 _ = lines.next(); // Skip SPDX identifier
204208
......@@ -221,7 +225,7 @@ pub fn main() !void {
221225 , .{version});
222226
223227 for (architectures, 0..) |arch, i| {
224 const table = try linux_dir.readFile(switch (arch.table) {
228 const table = try linux_dir.readFile(io, switch (arch.table) {
225229 .generic => "scripts/syscall.tbl",
226230 .specific => |f| f,
227231 }, buf);
tools/incr-check.zig+125-71
......@@ -1,9 +1,10 @@
11const std = @import("std");
22const Io = std.Io;
3const Dir = std.Io.Dir;
34const Allocator = std.mem.Allocator;
45const Cache = std.Build.Cache;
56
6const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-dwarf] [--debug-link] [--preserve-tmp] [--zig-cc-binary /path/to/zig]";
7const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-log foo] [--preserve-tmp] [--zig-cc-binary /path/to/zig]";
78
89pub fn main() !void {
910 const fatal = std.process.fatal;
......@@ -14,7 +15,7 @@ pub fn main() !void {
1415
1516 const gpa = arena;
1617
17 var threaded: Io.Threaded = .init(gpa);
18 var threaded: Io.Threaded = .init(gpa, .{});
1819 defer threaded.deinit();
1920 const io = threaded.io();
2021
......@@ -22,27 +23,37 @@ pub fn main() !void {
2223 var opt_input_file_name: ?[]const u8 = null;
2324 var opt_lib_dir: ?[]const u8 = null;
2425 var opt_cc_zig: ?[]const u8 = null;
25 var debug_zcu = false;
26 var debug_dwarf = false;
27 var debug_link = false;
2826 var preserve_tmp = false;
27 var enable_qemu: bool = false;
28 var enable_wine: bool = false;
29 var enable_wasmtime: bool = false;
30 var enable_darling: bool = false;
31
32 var debug_log_args: std.ArrayList([]const u8) = .empty;
2933
3034 var arg_it = try std.process.argsWithAllocator(arena);
3135 _ = arg_it.skip();
3236 while (arg_it.next()) |arg| {
3337 if (arg.len > 0 and arg[0] == '-') {
3438 if (std.mem.eql(u8, arg, "--zig-lib-dir")) {
35 opt_lib_dir = arg_it.next() orelse fatal("expected arg after '--zig-lib-dir'\n{s}", .{usage});
36 } else if (std.mem.eql(u8, arg, "--debug-zcu")) {
37 debug_zcu = true;
38 } else if (std.mem.eql(u8, arg, "--debug-dwarf")) {
39 debug_dwarf = true;
40 } else if (std.mem.eql(u8, arg, "--debug-link")) {
41 debug_link = true;
39 opt_lib_dir = arg_it.next() orelse fatal("expected arg after --zig-lib-dir\n{s}", .{usage});
40 } else if (std.mem.eql(u8, arg, "--debug-log")) {
41 try debug_log_args.append(
42 arena,
43 arg_it.next() orelse fatal("expected arg after --debug-log\n{s}", .{usage}),
44 );
4245 } else if (std.mem.eql(u8, arg, "--preserve-tmp")) {
4346 preserve_tmp = true;
47 } else if (std.mem.eql(u8, arg, "-fqemu")) {
48 enable_qemu = true;
49 } else if (std.mem.eql(u8, arg, "-fwine")) {
50 enable_wine = true;
51 } else if (std.mem.eql(u8, arg, "-fwasmtime")) {
52 enable_wasmtime = true;
53 } else if (std.mem.eql(u8, arg, "-fdarling")) {
54 enable_darling = true;
4455 } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) {
45 opt_cc_zig = arg_it.next() orelse fatal("expect arg after '--zig-cc-binary'\n{s}", .{usage});
56 opt_cc_zig = arg_it.next() orelse fatal("expected arg after --zig-cc-binary\n{s}", .{usage});
4657 } else {
4758 fatal("unknown option '{s}'\n{s}", .{ arg, usage });
4859 }
......@@ -59,7 +70,7 @@ pub fn main() !void {
5970 const zig_exe = opt_zig_exe orelse fatal("missing path to zig\n{s}", .{usage});
6071 const input_file_name = opt_input_file_name orelse fatal("missing input file\n{s}", .{usage});
6172
62 const input_file_bytes = try std.fs.cwd().readFileAlloc(input_file_name, arena, .limited(std.math.maxInt(u32)));
73 const input_file_bytes = try Dir.cwd().readFileAlloc(io, input_file_name, arena, .limited(std.math.maxInt(u32)));
6374 const case = try Case.parse(arena, io, input_file_bytes);
6475
6576 // Check now: if there are any targets using the `cbe` backend, we need the lib dir.
......@@ -71,31 +82,31 @@ pub fn main() !void {
7182 }
7283 }
7384
74 const prog_node = std.Progress.start(.{});
85 const prog_node = std.Progress.start(io, .{});
7586 defer prog_node.end();
7687
7788 const rand_int = std.crypto.random.int(u64);
7889 const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);
79 var tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{});
90 var tmp_dir = try Dir.cwd().createDirPathOpen(io, tmp_dir_path, .{});
8091 defer {
81 tmp_dir.close();
92 tmp_dir.close(io);
8293 if (!preserve_tmp) {
83 std.fs.cwd().deleteTree(tmp_dir_path) catch |err| {
84 std.log.warn("failed to delete tree '{s}': {s}", .{ tmp_dir_path, @errorName(err) });
94 Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| {
95 std.log.warn("failed to delete tree '{s}': {t}", .{ tmp_dir_path, err });
8596 };
8697 }
8798 }
8899
89100 // Convert paths to be relative to the cwd of the subprocess.
90 const resolved_zig_exe = try std.fs.path.relative(arena, tmp_dir_path, zig_exe);
101 const resolved_zig_exe = try Dir.path.relative(arena, tmp_dir_path, zig_exe);
91102 const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir|
92 try std.fs.path.relative(arena, tmp_dir_path, lib_dir)
103 try Dir.path.relative(arena, tmp_dir_path, lib_dir)
93104 else
94105 null;
95106
96107 const host = try std.zig.system.resolveTargetQuery(io, .{});
97108
98 const debug_log_verbose = debug_zcu or debug_dwarf or debug_link;
109 const debug_log_verbose = debug_log_args.items.len != 0;
99110
100111 for (case.targets) |target| {
101112 const target_prog_node = node: {
......@@ -133,14 +144,8 @@ pub fn main() !void {
133144 .llvm => try child_args.appendSlice(arena, &.{ "-fllvm", "-flld" }),
134145 .cbe => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }),
135146 }
136 if (debug_zcu) {
137 try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" });
138 }
139 if (debug_dwarf) {
140 try child_args.appendSlice(arena, &.{ "--debug-log", "dwarf" });
141 }
142 if (debug_link) {
143 try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" });
147 for (debug_log_args.items) |arg| {
148 try child_args.appendSlice(arena, &.{ "--debug-log", arg });
144149 }
145150 for (case.modules) |mod| {
146151 try child_args.appendSlice(arena, &.{ "--dep", mod.name });
......@@ -164,7 +169,7 @@ pub fn main() !void {
164169 var cc_child_args: std.ArrayList([]const u8) = .empty;
165170 if (target.backend == .cbe) {
166171 const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe|
167 try std.fs.path.relative(arena, tmp_dir_path, cc_zig_exe)
172 try Dir.path.relative(arena, tmp_dir_path, cc_zig_exe)
168173 else
169174 resolved_zig_exe;
170175
......@@ -185,6 +190,7 @@ pub fn main() !void {
185190
186191 var eval: Eval = .{
187192 .arena = arena,
193 .io = io,
188194 .case = case,
189195 .host = host,
190196 .target = target,
......@@ -194,11 +200,15 @@ pub fn main() !void {
194200 .allow_stderr = debug_log_verbose,
195201 .preserve_tmp_on_fatal = preserve_tmp,
196202 .cc_child_args = &cc_child_args,
203 .enable_qemu = enable_qemu,
204 .enable_wine = enable_wine,
205 .enable_wasmtime = enable_wasmtime,
206 .enable_darling = enable_darling,
197207 };
198208
199 try child.spawn();
209 try child.spawn(io);
200210 errdefer {
201 _ = child.kill() catch {};
211 _ = child.kill(io) catch {};
202212 }
203213
204214 var poller = Io.poll(arena, Eval.StreamEnum, .{
......@@ -228,10 +238,11 @@ pub fn main() !void {
228238
229239const Eval = struct {
230240 arena: Allocator,
241 io: Io,
231242 host: std.Target,
232243 case: Case,
233244 target: Case.Target,
234 tmp_dir: std.fs.Dir,
245 tmp_dir: Dir,
235246 tmp_dir_path: []const u8,
236247 child: *std.process.Child,
237248 allow_stderr: bool,
......@@ -240,22 +251,28 @@ const Eval = struct {
240251 /// The arguments `out.c in.c` must be appended before spawning the subprocess.
241252 cc_child_args: *std.ArrayList([]const u8),
242253
254 enable_qemu: bool,
255 enable_wine: bool,
256 enable_wasmtime: bool,
257 enable_darling: bool,
258
243259 const StreamEnum = enum { stdout, stderr };
244260 const Poller = Io.Poller(StreamEnum);
245261
246262 /// Currently this function assumes the previous updates have already been written.
247263 fn write(eval: *Eval, update: Case.Update) void {
264 const io = eval.io;
248265 for (update.changes) |full_contents| {
249 eval.tmp_dir.writeFile(.{
266 eval.tmp_dir.writeFile(io, .{
250267 .sub_path = full_contents.name,
251268 .data = full_contents.bytes,
252269 }) catch |err| {
253 eval.fatal("failed to update '{s}': {s}", .{ full_contents.name, @errorName(err) });
270 eval.fatal("failed to update '{s}': {t}", .{ full_contents.name, err });
254271 };
255272 }
256273 for (update.deletes) |doomed_name| {
257 eval.tmp_dir.deleteFile(doomed_name) catch |err| {
258 eval.fatal("failed to delete '{s}': {s}", .{ doomed_name, @errorName(err) });
274 eval.tmp_dir.deleteFile(io, doomed_name) catch |err| {
275 eval.fatal("failed to delete '{s}': {t}", .{ doomed_name, err });
259276 };
260277 }
261278 }
......@@ -307,14 +324,14 @@ const Eval = struct {
307324 }
308325
309326 const digest = r.takeArray(Cache.bin_digest_len) catch unreachable;
310 const result_dir = ".local-cache" ++ std.fs.path.sep_str ++ "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*);
327 const result_dir = ".local-cache" ++ Dir.path.sep_str ++ "o" ++ Dir.path.sep_str ++ Cache.binToHex(digest.*);
311328
312329 const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
313330 .root_name = "root", // corresponds to the module name "root"
314331 .target = &eval.target.resolved,
315332 .output_mode = .Exe,
316333 });
317 const bin_path = try std.fs.path.join(arena, &.{ result_dir, bin_name });
334 const bin_path = try Dir.path.join(arena, &.{ result_dir, bin_name });
318335
319336 try eval.checkSuccessOutcome(update, bin_path, prog_node);
320337 // This message indicates the end of the update.
......@@ -338,11 +355,12 @@ const Eval = struct {
338355 }
339356
340357 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
358 const io = eval.io;
341359 const expected = switch (update.outcome) {
342360 .unknown => return,
343361 .compile_errors => |ce| ce,
344362 .stdout, .exit_code => {
345 error_bundle.renderToStdErr(.{}, .auto);
363 try error_bundle.renderToStderr(io, .{}, .auto);
346364 eval.fatal("update '{s}': unexpected compile errors", .{update.name});
347365 },
348366 };
......@@ -351,7 +369,7 @@ const Eval = struct {
351369
352370 for (error_bundle.getMessages()) |err_idx| {
353371 if (expected_idx == expected.errors.len) {
354 error_bundle.renderToStdErr(.{}, .auto);
372 try error_bundle.renderToStderr(io, .{}, .auto);
355373 eval.fatal("update '{s}': more errors than expected", .{update.name});
356374 }
357375 try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], false, err_idx);
......@@ -359,7 +377,7 @@ const Eval = struct {
359377
360378 for (error_bundle.getNotes(err_idx)) |note_idx| {
361379 if (expected_idx == expected.errors.len) {
362 error_bundle.renderToStdErr(.{}, .auto);
380 try error_bundle.renderToStderr(io, .{}, .auto);
363381 eval.fatal("update '{s}': more error notes than expected", .{update.name});
364382 }
365383 try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], true, note_idx);
......@@ -368,7 +386,7 @@ const Eval = struct {
368386 }
369387
370388 if (!std.mem.eql(u8, error_bundle.getCompileLogOutput(), expected.compile_log_output)) {
371 error_bundle.renderToStdErr(.{}, .auto);
389 try error_bundle.renderToStderr(io, .{}, .auto);
372390 eval.fatal("update '{s}': unexpected compile log output", .{update.name});
373391 }
374392 }
......@@ -388,6 +406,8 @@ const Eval = struct {
388406 const src = eb.getSourceLocation(err.src_loc);
389407 const raw_filename = eb.nullTerminatedString(src.src_path);
390408
409 const io = eval.io;
410
391411 // We need to replace backslashes for consistency between platforms.
392412 const filename = name: {
393413 if (std.mem.indexOfScalar(u8, raw_filename, '\\') == null) break :name raw_filename;
......@@ -402,7 +422,7 @@ const Eval = struct {
402422 expected.column != src.column + 1 or
403423 !std.mem.eql(u8, expected.msg, msg))
404424 {
405 eb.renderToStdErr(.{}, .auto);
425 eb.renderToStderr(io, .{}, .auto) catch {};
406426 eval.fatal("update '{s}': compile error did not match expected error", .{update.name});
407427 }
408428 }
......@@ -429,8 +449,11 @@ const Eval = struct {
429449 },
430450 };
431451
452 const io = eval.io;
453
432454 var argv_buf: [2][]const u8 = undefined;
433 const argv: []const []const u8, const is_foreign: bool = switch (std.zig.system.getExternalExecutor(
455 const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor(
456 io,
434457 &eval.host,
435458 &eval.target.resolved,
436459 .{ .link_libc = eval.target.backend == .cbe },
......@@ -449,18 +472,48 @@ const Eval = struct {
449472 argv_buf[0] = binary_path;
450473 break :argv .{ argv_buf[0..1], false };
451474 },
452 .qemu, .wine, .wasmtime, .darling => |executor_cmd| argv: {
453 argv_buf[0] = executor_cmd;
454 argv_buf[1] = binary_path;
455 break :argv .{ argv_buf[0..2], true };
475 .qemu => |executor_cmd| argv: {
476 if (eval.enable_qemu) {
477 argv_buf[0] = executor_cmd;
478 argv_buf[1] = binary_path;
479 break :argv .{ argv_buf[0..2], true };
480 } else {
481 continue :sw .bad_os_or_cpu;
482 }
483 },
484 .wine => |executor_cmd| argv: {
485 if (eval.enable_wine) {
486 argv_buf[0] = executor_cmd;
487 argv_buf[1] = binary_path;
488 break :argv .{ argv_buf[0..2], true };
489 } else {
490 continue :sw .bad_os_or_cpu;
491 }
492 },
493 .wasmtime => |executor_cmd| argv: {
494 if (eval.enable_wasmtime) {
495 argv_buf[0] = executor_cmd;
496 argv_buf[1] = binary_path;
497 break :argv .{ argv_buf[0..2], true };
498 } else {
499 continue :sw .bad_os_or_cpu;
500 }
501 },
502 .darling => |executor_cmd| argv: {
503 if (eval.enable_darling) {
504 argv_buf[0] = executor_cmd;
505 argv_buf[1] = binary_path;
506 break :argv .{ argv_buf[0..2], true };
507 } else {
508 continue :sw .bad_os_or_cpu;
509 }
456510 },
457511 };
458512
459513 const run_prog_node = prog_node.start("run generated executable", 0);
460514 defer run_prog_node.end();
461515
462 const result = std.process.Child.run(.{
463 .allocator = eval.arena,
516 const result = std.process.Child.run(eval.arena, io, .{
464517 .argv = argv,
465518 .cwd_dir = eval.tmp_dir,
466519 .cwd = eval.tmp_dir_path,
......@@ -468,17 +521,17 @@ const Eval = struct {
468521 if (is_foreign) {
469522 // Chances are the foreign executor isn't available. Skip this evaluation.
470523 if (eval.allow_stderr) {
471 std.log.warn("update '{s}': skipping execution of '{s}' via executor for foreign target '{s}': {s}", .{
524 std.log.warn("update '{s}': skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{
472525 update.name,
473526 binary_path,
474527 try eval.target.resolved.zigTriple(eval.arena),
475 @errorName(err),
528 err,
476529 });
477530 }
478531 return;
479532 }
480 eval.fatal("update '{s}': failed to run the generated executable '{s}': {s}", .{
481 update.name, binary_path, @errorName(err),
533 eval.fatal("update '{s}': failed to run the generated executable '{s}': {t}", .{
534 update.name, binary_path, err,
482535 });
483536 };
484537
......@@ -514,11 +567,12 @@ const Eval = struct {
514567 }
515568
516569 fn requestUpdate(eval: *Eval) !void {
570 const io = eval.io;
517571 const header: std.zig.Client.Message.Header = .{
518572 .tag = .update,
519573 .bytes_len = 0,
520574 };
521 var w = eval.child.stdin.?.writer(&.{});
575 var w = eval.child.stdin.?.writer(io, &.{});
522576 w.interface.writeStruct(header, .little) catch |err| switch (err) {
523577 error.WriteFailed => return w.err.?,
524578 };
......@@ -552,16 +606,13 @@ const Eval = struct {
552606 try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path });
553607 defer eval.cc_child_args.items.len -= 2;
554608
555 const result = std.process.Child.run(.{
556 .allocator = eval.arena,
609 const result = std.process.Child.run(eval.arena, eval.io, .{
557610 .argv = eval.cc_child_args.items,
558611 .cwd_dir = eval.tmp_dir,
559612 .cwd = eval.tmp_dir_path,
560613 .progress_node = child_prog_node,
561614 }) catch |err| {
562 eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{
563 update.name, c_path, @errorName(err),
564 });
615 eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {t}", .{ update.name, c_path, err });
565616 };
566617 switch (result.term) {
567618 .Exited => |code| if (code != 0) {
......@@ -588,12 +639,13 @@ const Eval = struct {
588639 }
589640
590641 fn fatal(eval: *Eval, comptime fmt: []const u8, args: anytype) noreturn {
591 eval.tmp_dir.close();
642 const io = eval.io;
643 eval.tmp_dir.close(io);
592644 if (!eval.preserve_tmp_on_fatal) {
593645 // Kill the child since it holds an open handle to its CWD which is the tmp dir path
594 _ = eval.child.kill() catch {};
595 std.fs.cwd().deleteTree(eval.tmp_dir_path) catch |err| {
596 std.log.warn("failed to delete tree '{s}': {s}", .{ eval.tmp_dir_path, @errorName(err) });
646 _ = eval.child.kill(io) catch {};
647 Dir.cwd().deleteTree(io, eval.tmp_dir_path) catch |err| {
648 std.log.warn("failed to delete tree '{s}': {t}", .{ eval.tmp_dir_path, err });
597649 };
598650 }
599651 std.process.fatal(fmt, args);
......@@ -759,7 +811,7 @@ const Case = struct {
759811 if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n});
760812 last_update.outcome = .{
761813 .stdout = std.zig.string_literal.parseAlloc(arena, val) catch |err| {
762 fatal("line {d}: bad string literal: {s}", .{ line_n, @errorName(err) });
814 fatal("line {d}: bad string literal: {t}", .{ line_n, err });
763815 },
764816 };
765817 } else if (std.mem.eql(u8, key, "expect_error")) {
......@@ -833,27 +885,29 @@ const Case = struct {
833885
834886fn requestExit(child: *std.process.Child, eval: *Eval) void {
835887 if (child.stdin == null) return;
888 const io = eval.io;
836889
837890 const header: std.zig.Client.Message.Header = .{
838891 .tag = .exit,
839892 .bytes_len = 0,
840893 };
841 var w = eval.child.stdin.?.writer(&.{});
894 var w = eval.child.stdin.?.writer(io, &.{});
842895 w.interface.writeStruct(header, .little) catch |err| switch (err) {
843896 error.WriteFailed => switch (w.err.?) {
844897 error.BrokenPipe => {},
845 else => |e| eval.fatal("failed to send exit: {s}", .{@errorName(e)}),
898 else => |e| eval.fatal("failed to send exit: {t}", .{e}),
846899 },
847900 };
848901
849902 // Send EOF to stdin.
850 child.stdin.?.close();
903 child.stdin.?.close(io);
851904 child.stdin = null;
852905}
853906
854907fn waitChild(child: *std.process.Child, eval: *Eval) void {
908 const io = eval.io;
855909 requestExit(child, eval);
856 const term = child.wait() catch |err| eval.fatal("child process failed: {s}", .{@errorName(err)});
910 const term = child.wait(io) catch |err| eval.fatal("child process failed: {t}", .{err});
857911 switch (term) {
858912 .Exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}),
859913 .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}),
tools/migrate_langref.zig+19-16
......@@ -1,13 +1,16 @@
1const std = @import("std");
21const builtin = @import("builtin");
3const fs = std.fs;
2
3const std = @import("std");
4const Io = std.Io;
5const Dir = std.Io.Dir;
46const print = std.debug.print;
57const mem = std.mem;
68const testing = std.testing;
79const Allocator = std.mem.Allocator;
8const max_doc_file_size = 10 * 1024 * 1024;
910const fatal = std.process.fatal;
1011
12const max_doc_file_size = 10 * 1024 * 1024;
13
1114pub fn main() !void {
1215 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
1316 defer arena_instance.deinit();
......@@ -19,27 +22,27 @@ pub fn main() !void {
1922 const input_file = args[1];
2023 const output_file = args[2];
2124
22 var threaded: std.Io.Threaded = .init(gpa);
25 var threaded: std.Io.Threaded = .init(gpa, .{});
2326 defer threaded.deinit();
2427 const io = threaded.io();
2528
26 var in_file = try fs.cwd().openFile(input_file, .{ .mode = .read_only });
27 defer in_file.close();
29 var in_file = try Dir.cwd().openFile(io, input_file, .{ .mode = .read_only });
30 defer in_file.close(io);
2831
29 var out_file = try fs.cwd().createFile(output_file, .{});
30 defer out_file.close();
32 var out_file = try Dir.cwd().createFile(io, output_file, .{});
33 defer out_file.close(io);
3134 var out_file_buffer: [4096]u8 = undefined;
32 var out_file_writer = out_file.writer(&out_file_buffer);
35 var out_file_writer = out_file.writer(io, &out_file_buffer);
3336
34 var out_dir = try fs.cwd().openDir(fs.path.dirname(output_file).?, .{});
35 defer out_dir.close();
37 var out_dir = try Dir.cwd().openDir(io, Dir.path.dirname(output_file).?, .{});
38 defer out_dir.close(io);
3639
3740 var in_file_reader = in_file.reader(io, &.{});
3841 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .unlimited);
3942
4043 var tokenizer = Tokenizer.init(input_file, input_file_bytes);
4144
42 try walk(arena, &tokenizer, out_dir, &out_file_writer.interface);
45 try walk(arena, io, &tokenizer, out_dir, &out_file_writer.interface);
4346
4447 try out_file_writer.end();
4548}
......@@ -266,7 +269,7 @@ const Code = struct {
266269 };
267270};
268271
269fn walk(arena: Allocator, tokenizer: *Tokenizer, out_dir: std.fs.Dir, w: anytype) !void {
272fn walk(arena: Allocator, io: Io, tokenizer: *Tokenizer, out_dir: Dir, w: anytype) !void {
270273 while (true) {
271274 const token = tokenizer.next();
272275 switch (token.id) {
......@@ -384,12 +387,12 @@ fn walk(arena: Allocator, tokenizer: *Tokenizer, out_dir: std.fs.Dir, w: anytype
384387
385388 const basename = try std.fmt.allocPrint(arena, "{s}.zig", .{name});
386389
387 var file = out_dir.createFile(basename, .{ .exclusive = true }) catch |err| {
390 var file = out_dir.createFile(io, basename, .{ .exclusive = true }) catch |err| {
388391 fatal("unable to create file '{s}': {s}", .{ name, @errorName(err) });
389392 };
390 defer file.close();
393 defer file.close(io);
391394 var file_buffer: [1024]u8 = undefined;
392 var file_writer = file.writer(&file_buffer);
395 var file_writer = file.writer(io, &file_buffer);
393396 const code = &file_writer.interface;
394397
395398 const source = tokenizer.buffer[source_token.start..source_token.end];
tools/process_headers.zig+22-17
......@@ -12,6 +12,8 @@
1212//! You'll then have to manually update Zig source repo with these new files.
1313
1414const std = @import("std");
15const Io = std.Io;
16const Dir = std.Io.Dir;
1517const Arch = std.Target.Cpu.Arch;
1618const Abi = std.Target.Abi;
1719const OsTag = std.Target.Os.Tag;
......@@ -128,6 +130,11 @@ const LibCVendor = enum {
128130pub fn main() !void {
129131 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
130132 const allocator = arena.allocator();
133
134 var threaded: Io.Threaded = .init(allocator, .{});
135 defer threaded.deinit();
136 const io = threaded.io();
137
131138 const args = try std.process.argsAlloc(allocator);
132139 var search_paths = std.array_list.Managed([]const u8).init(allocator);
133140 var opt_out_dir: ?[]const u8 = null;
......@@ -232,28 +239,28 @@ pub fn main() !void {
232239 => &[_][]const u8{ search_path, libc_dir, "usr", "include" },
233240 .musl => &[_][]const u8{ search_path, libc_dir, "usr", "local", "musl", "include" },
234241 };
235 const target_include_dir = try std.fs.path.join(allocator, sub_path);
242 const target_include_dir = try Dir.path.join(allocator, sub_path);
236243 var dir_stack = std.array_list.Managed([]const u8).init(allocator);
237244 try dir_stack.append(target_include_dir);
238245
239246 while (dir_stack.pop()) |full_dir_name| {
240 var dir = std.fs.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) {
247 var dir = Dir.cwd().openDir(io, full_dir_name, .{ .iterate = true }) catch |err| switch (err) {
241248 error.FileNotFound => continue :search,
242249 error.AccessDenied => continue :search,
243250 else => return err,
244251 };
245 defer dir.close();
252 defer dir.close(io);
246253
247254 var dir_it = dir.iterate();
248255
249 while (try dir_it.next()) |entry| {
250 const full_path = try std.fs.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name });
256 while (try dir_it.next(io)) |entry| {
257 const full_path = try Dir.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name });
251258 switch (entry.kind) {
252259 .directory => try dir_stack.append(full_path),
253260 .file, .sym_link => {
254 const rel_path = try std.fs.path.relative(allocator, target_include_dir, full_path);
261 const rel_path = try Dir.path.relative(allocator, target_include_dir, full_path);
255262 const max_size = 2 * 1024 * 1024 * 1024;
256 const raw_bytes = try std.fs.cwd().readFileAlloc(full_path, allocator, .limited(max_size));
263 const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, allocator, .limited(max_size));
257264 const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t");
258265 total_bytes += raw_bytes.len;
259266 const hash = try allocator.alloc(u8, 32);
......@@ -266,9 +273,7 @@ pub fn main() !void {
266273 max_bytes_saved += raw_bytes.len;
267274 gop.value_ptr.hit_count += 1;
268275 std.debug.print("duplicate: {s} {s} ({B})\n", .{
269 libc_dir,
270 rel_path,
271 raw_bytes.len,
276 libc_dir, rel_path, raw_bytes.len,
272277 });
273278 } else {
274279 gop.value_ptr.* = Contents{
......@@ -314,7 +319,7 @@ pub fn main() !void {
314319 total_bytes,
315320 total_bytes - max_bytes_saved,
316321 });
317 try std.fs.cwd().makePath(out_dir);
322 try Dir.cwd().createDirPath(io, out_dir);
318323
319324 var missed_opportunity_bytes: usize = 0;
320325 // iterate path_table. for each path, put all the hashes into a list. sort by hit_count.
......@@ -334,9 +339,9 @@ pub fn main() !void {
334339 const best_contents = contents_list.pop().?;
335340 if (best_contents.hit_count > 1) {
336341 // worth it to make it generic
337 const full_path = try std.fs.path.join(allocator, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* });
338 try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?);
339 try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = best_contents.bytes });
342 const full_path = try Dir.path.join(allocator, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* });
343 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);
344 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes });
340345 best_contents.is_generic = true;
341346 while (contents_list.pop()) |contender| {
342347 if (contender.hit_count > 1) {
......@@ -355,9 +360,9 @@ pub fn main() !void {
355360 if (contents.is_generic) continue;
356361
357362 const dest_target = hash_kv.key_ptr.*;
358 const full_path = try std.fs.path.join(allocator, &[_][]const u8{ out_dir, dest_target, path_kv.key_ptr.* });
359 try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?);
360 try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = contents.bytes });
363 const full_path = try Dir.path.join(allocator, &[_][]const u8{ out_dir, dest_target, path_kv.key_ptr.* });
364 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);
365 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes });
361366 }
362367 }
363368}
tools/update-linux-headers.zig+23-16
......@@ -15,6 +15,8 @@
1515//! You'll then have to manually update Zig source repo with these new files.
1616
1717const std = @import("std");
18const Io = std.Io;
19const Dir = std.Io.Dir;
1820const Arch = std.Target.Cpu.Arch;
1921const Abi = std.Target.Abi;
2022const assert = std.debug.assert;
......@@ -142,6 +144,11 @@ const PathTable = std.StringHashMap(*TargetToHash);
142144pub fn main() !void {
143145 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
144146 const arena = arena_state.allocator();
147
148 var threaded: Io.Threaded = .init(arena, .{});
149 defer threaded.deinit();
150 const io = threaded.io();
151
145152 const args = try std.process.argsAlloc(arena);
146153 var search_paths = std.array_list.Managed([]const u8).init(arena);
147154 var opt_out_dir: ?[]const u8 = null;
......@@ -183,30 +190,30 @@ pub fn main() !void {
183190 .arch = linux_target.arch,
184191 };
185192 search: for (search_paths.items) |search_path| {
186 const target_include_dir = try std.fs.path.join(arena, &.{
193 const target_include_dir = try Dir.path.join(arena, &.{
187194 search_path, linux_target.name, "include",
188195 });
189196 var dir_stack = std.array_list.Managed([]const u8).init(arena);
190197 try dir_stack.append(target_include_dir);
191198
192199 while (dir_stack.pop()) |full_dir_name| {
193 var dir = std.fs.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) {
200 var dir = Dir.cwd().openDir(io, full_dir_name, .{ .iterate = true }) catch |err| switch (err) {
194201 error.FileNotFound => continue :search,
195202 error.AccessDenied => continue :search,
196203 else => return err,
197204 };
198 defer dir.close();
205 defer dir.close(io);
199206
200207 var dir_it = dir.iterate();
201208
202 while (try dir_it.next()) |entry| {
203 const full_path = try std.fs.path.join(arena, &[_][]const u8{ full_dir_name, entry.name });
209 while (try dir_it.next(io)) |entry| {
210 const full_path = try Dir.path.join(arena, &[_][]const u8{ full_dir_name, entry.name });
204211 switch (entry.kind) {
205212 .directory => try dir_stack.append(full_path),
206213 .file => {
207 const rel_path = try std.fs.path.relative(arena, target_include_dir, full_path);
214 const rel_path = try Dir.path.relative(arena, target_include_dir, full_path);
208215 const max_size = 2 * 1024 * 1024 * 1024;
209 const raw_bytes = try std.fs.cwd().readFileAlloc(full_path, arena, .limited(max_size));
216 const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, arena, .limited(max_size));
210217 const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t");
211218 total_bytes += raw_bytes.len;
212219 const hash = try arena.alloc(u8, 32);
......@@ -253,7 +260,7 @@ pub fn main() !void {
253260 total_bytes,
254261 total_bytes - max_bytes_saved,
255262 });
256 try std.fs.cwd().makePath(out_dir);
263 try Dir.cwd().createDirPath(io, out_dir);
257264
258265 var missed_opportunity_bytes: usize = 0;
259266 // iterate path_table. for each path, put all the hashes into a list. sort by hit_count.
......@@ -273,9 +280,9 @@ pub fn main() !void {
273280 const best_contents = contents_list.pop().?;
274281 if (best_contents.hit_count > 1) {
275282 // worth it to make it generic
276 const full_path = try std.fs.path.join(arena, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* });
277 try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?);
278 try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = best_contents.bytes });
283 const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* });
284 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);
285 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes });
279286 best_contents.is_generic = true;
280287 while (contents_list.pop()) |contender| {
281288 if (contender.hit_count > 1) {
......@@ -299,9 +306,9 @@ pub fn main() !void {
299306 else => @tagName(dest_target.arch),
300307 };
301308 const out_subpath = try std.fmt.allocPrint(arena, "{s}-linux-any", .{arch_name});
302 const full_path = try std.fs.path.join(arena, &[_][]const u8{ out_dir, out_subpath, path_kv.key_ptr.* });
303 try std.fs.cwd().makePath(std.fs.path.dirname(full_path).?);
304 try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = contents.bytes });
309 const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, out_subpath, path_kv.key_ptr.* });
310 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);
311 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes });
305312 }
306313 }
307314
......@@ -316,8 +323,8 @@ pub fn main() !void {
316323 "any-linux-any/linux/netfilter_ipv6/ip6t_HL.h",
317324 };
318325 for (bad_files) |bad_file| {
319 const full_path = try std.fs.path.join(arena, &[_][]const u8{ out_dir, bad_file });
320 try std.fs.cwd().deleteFile(full_path);
326 const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, bad_file });
327 try Dir.cwd().deleteFile(io, full_path);
321328 }
322329}
323330
tools/update_clang_options.zig+9-6
......@@ -10,7 +10,7 @@
1010//! would mean that the next parameter specifies the target.
1111
1212const std = @import("std");
13const fs = std.fs;
13const Io = std.Io;
1414const assert = std.debug.assert;
1515const json = std.json;
1616
......@@ -634,8 +634,12 @@ pub fn main() anyerror!void {
634634 const allocator = arena.allocator();
635635 const args = try std.process.argsAlloc(allocator);
636636
637 var threaded: std.Io.Threaded = .init(allocator, .{});
638 defer threaded.deinit();
639 const io = threaded.io();
640
637641 var stdout_buffer: [4000]u8 = undefined;
638 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
642 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
639643 const stdout = &stdout_writer.interface;
640644
641645 if (args.len <= 1) printUsageAndExit(args[0]);
......@@ -676,8 +680,7 @@ pub fn main() anyerror!void {
676680 try std.fmt.allocPrint(allocator, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}),
677681 };
678682
679 const child_result = try std.process.Child.run(.{
680 .allocator = allocator,
683 const child_result = try std.process.Child.run(allocator, io, .{
681684 .argv = &child_args,
682685 .max_output_bytes = 100 * 1024 * 1024,
683686 });
......@@ -961,8 +964,8 @@ fn objectLessThan(context: void, a: *json.ObjectMap, b: *json.ObjectMap) bool {
961964}
962965
963966fn printUsageAndExit(arg0: []const u8) noreturn {
964 const w, _ = std.debug.lockStderrWriter(&.{});
965 defer std.debug.unlockStderrWriter();
967 const stderr = std.debug.lockStderr(&.{});
968 const w = &stderr.file_writer.interface;
966969 printUsage(w, arg0) catch std.process.exit(2);
967970 std.process.exit(1);
968971}
tools/update_cpu_features.zig+22-20
......@@ -1,6 +1,8 @@
1const std = @import("std");
21const builtin = @import("builtin");
3const fs = std.fs;
2
3const std = @import("std");
4const Io = std.Io;
5const Dir = std.Io.Dir;
46const mem = std.mem;
57const json = std.json;
68const assert = std.debug.assert;
......@@ -1890,7 +1892,7 @@ pub fn main() anyerror!void {
18901892 defer arena_state.deinit();
18911893 const arena = arena_state.allocator();
18921894
1893 var threaded: std.Io.Threaded = .init(gpa);
1895 var threaded: std.Io.Threaded = .init(gpa, .{});
18941896 defer threaded.deinit();
18951897 const io = threaded.io();
18961898
......@@ -1927,26 +1929,26 @@ pub fn main() anyerror!void {
19271929 // there shouldn't be any more argument after the optional filter
19281930 if (args.skip()) usageAndExit(args0, 1);
19291931
1930 var zig_src_dir = try fs.cwd().openDir(zig_src_root, .{});
1931 defer zig_src_dir.close();
1932 var zig_src_dir = try Dir.cwd().openDir(io, zig_src_root, .{});
1933 defer zig_src_dir.close(io);
19321934
1933 const root_progress = std.Progress.start(.{ .estimated_total_items = targets.len });
1935 const root_progress = std.Progress.start(io, .{ .estimated_total_items = targets.len });
19341936 defer root_progress.end();
19351937
1936 var group: std.Io.Group = .init;
1938 var group: Io.Group = .init;
19371939 defer group.cancel(io);
19381940
19391941 for (targets) |target| {
19401942 if (filter) |zig_name| {
19411943 if (!std.mem.eql(u8, target.zig_name, zig_name)) continue;
19421944 }
1943 group.async(io, processOneTarget, .{.{
1945 group.async(io, processOneTarget, .{ io, .{
19441946 .llvm_tblgen_exe = llvm_tblgen_exe,
19451947 .llvm_src_root = llvm_src_root,
19461948 .zig_src_dir = zig_src_dir,
19471949 .root_progress = root_progress,
19481950 .target = target,
1949 }});
1951 } });
19501952 }
19511953
19521954 group.wait(io);
......@@ -1955,12 +1957,12 @@ pub fn main() anyerror!void {
19551957const Job = struct {
19561958 llvm_tblgen_exe: []const u8,
19571959 llvm_src_root: []const u8,
1958 zig_src_dir: std.fs.Dir,
1960 zig_src_dir: Dir,
19591961 root_progress: std.Progress.Node,
19601962 target: ArchTarget,
19611963};
19621964
1963fn processOneTarget(job: Job) void {
1965fn processOneTarget(io: Io, job: Job) void {
19641966 errdefer |err| std.debug.panic("panic: {s}", .{@errorName(err)});
19651967 const target = job.target;
19661968
......@@ -1992,8 +1994,7 @@ fn processOneTarget(job: Job) void {
19921994 }),
19931995 };
19941996
1995 const child_result = try std.process.Child.run(.{
1996 .allocator = arena,
1997 const child_result = try std.process.Child.run(arena, io, .{
19971998 .argv = &child_args,
19981999 .max_output_bytes = 500 * 1024 * 1024,
19992000 });
......@@ -2240,15 +2241,15 @@ fn processOneTarget(job: Job) void {
22402241
22412242 const render_progress = progress_node.start("rendering Zig code", 0);
22422243
2243 var target_dir = try job.zig_src_dir.openDir("lib/std/Target", .{});
2244 defer target_dir.close();
2244 var target_dir = try job.zig_src_dir.openDir(io, "lib/std/Target", .{});
2245 defer target_dir.close(io);
22452246
22462247 const zig_code_basename = try std.fmt.allocPrint(arena, "{s}.zig", .{target.zig_name});
2247 var zig_code_file = try target_dir.createFile(zig_code_basename, .{});
2248 defer zig_code_file.close();
2248 var zig_code_file = try target_dir.createFile(io, zig_code_basename, .{});
2249 defer zig_code_file.close(io);
22492250
22502251 var zig_code_file_buffer: [4096]u8 = undefined;
2251 var zig_code_file_writer = zig_code_file.writer(&zig_code_file_buffer);
2252 var zig_code_file_writer = zig_code_file.writer(io, &zig_code_file_buffer);
22522253 const w = &zig_code_file_writer.interface;
22532254
22542255 try w.writeAll(
......@@ -2424,8 +2425,9 @@ fn processOneTarget(job: Job) void {
24242425}
24252426
24262427fn usageAndExit(arg0: []const u8, code: u8) noreturn {
2427 const stderr, _ = std.debug.lockStderrWriter(&.{});
2428 stderr.print(
2428 const stderr = std.debug.lockStderr(&.{});
2429 const w = &stderr.file_writer.interface;
2430 w.print(
24292431 \\Usage: {s} /path/to/llvm-tblgen /path/git/llvm-project /path/git/zig [zig_name filter]
24302432 \\
24312433 \\Updates lib/std/target/<target>.zig from llvm/lib/Target/<Target>/<Target>.td .
tools/update_crc_catalog.zig+22-17
......@@ -1,5 +1,6 @@
11const std = @import("std");
2const fs = std.fs;
2const Io = std.Io;
3const Dir = std.Io.Dir;
34const mem = std.mem;
45const ascii = std.ascii;
56
......@@ -10,27 +11,31 @@ pub fn main() anyerror!void {
1011 defer arena_state.deinit();
1112 const arena = arena_state.allocator();
1213
14 var threaded: Io.Threaded = .init(arena, .{});
15 defer threaded.deinit();
16 const io = threaded.io();
17
1318 const args = try std.process.argsAlloc(arena);
1419 if (args.len <= 1) printUsageAndExit(args[0]);
1520
1621 const zig_src_root = args[1];
1722 if (mem.startsWith(u8, zig_src_root, "-")) printUsageAndExit(args[0]);
1823
19 var zig_src_dir = try fs.cwd().openDir(zig_src_root, .{});
20 defer zig_src_dir.close();
24 var zig_src_dir = try Dir.cwd().openDir(io, zig_src_root, .{});
25 defer zig_src_dir.close(io);
2126
22 const hash_sub_path = try fs.path.join(arena, &.{ "lib", "std", "hash" });
23 var hash_target_dir = try zig_src_dir.makeOpenPath(hash_sub_path, .{});
24 defer hash_target_dir.close();
27 const hash_sub_path = try Dir.path.join(arena, &.{ "lib", "std", "hash" });
28 var hash_target_dir = try zig_src_dir.createDirPathOpen(io, hash_sub_path, .{});
29 defer hash_target_dir.close(io);
2530
26 const crc_sub_path = try fs.path.join(arena, &.{ "lib", "std", "hash", "crc" });
27 var crc_target_dir = try zig_src_dir.makeOpenPath(crc_sub_path, .{});
28 defer crc_target_dir.close();
31 const crc_sub_path = try Dir.path.join(arena, &.{ "lib", "std", "hash", "crc" });
32 var crc_target_dir = try zig_src_dir.createDirPathOpen(io, crc_sub_path, .{});
33 defer crc_target_dir.close(io);
2934
30 var zig_code_file = try hash_target_dir.createFile("crc.zig", .{});
31 defer zig_code_file.close();
35 var zig_code_file = try hash_target_dir.createFile(io, "crc.zig", .{});
36 defer zig_code_file.close(io);
3237 var zig_code_file_buffer: [4096]u8 = undefined;
33 var zig_code_file_writer = zig_code_file.writer(&zig_code_file_buffer);
38 var zig_code_file_writer = zig_code_file.writer(io, &zig_code_file_buffer);
3439 const code_writer = &zig_code_file_writer.interface;
3540
3641 try code_writer.writeAll(
......@@ -51,10 +56,10 @@ pub fn main() anyerror!void {
5156 \\
5257 );
5358
54 var zig_test_file = try crc_target_dir.createFile("test.zig", .{});
55 defer zig_test_file.close();
59 var zig_test_file = try crc_target_dir.createFile(io, "test.zig", .{});
60 defer zig_test_file.close(io);
5661 var zig_test_file_buffer: [4096]u8 = undefined;
57 var zig_test_file_writer = zig_test_file.writer(&zig_test_file_buffer);
62 var zig_test_file_writer = zig_test_file.writer(io, &zig_test_file_buffer);
5863 const test_writer = &zig_test_file_writer.interface;
5964
6065 try test_writer.writeAll(
......@@ -190,8 +195,8 @@ pub fn main() anyerror!void {
190195}
191196
192197fn printUsageAndExit(arg0: []const u8) noreturn {
193 const w, _ = std.debug.lockStderrWriter(&.{});
194 defer std.debug.unlockStderrWriter();
198 const stderr = std.debug.lockStderr(&.{});
199 const w = &stderr.file_writer.interface;
195200 printUsage(w, arg0) catch std.process.exit(2);
196201 std.process.exit(1);
197202}
tools/update_freebsd_libc.zig+16-16
......@@ -5,6 +5,7 @@
55//! `zig run tools/update_freebsd_libc.zig -- ~/Downloads/freebsd-src .`
66
77const std = @import("std");
8const Io = std.Io;
89
910const exempt_files = [_][]const u8{
1011 // This file is maintained by a separate project and does not come from FreeBSD.
......@@ -16,29 +17,31 @@ pub fn main() !void {
1617 defer arena_instance.deinit();
1718 const arena = arena_instance.allocator();
1819
20 var threaded: Io.Threaded = .init(arena, .{});
21 defer threaded.deinit();
22 const io = threaded.io();
23
1924 const args = try std.process.argsAlloc(arena);
2025 const freebsd_src_path = args[1];
2126 const zig_src_path = args[2];
2227
2328 const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/freebsd", .{zig_src_path});
2429
25 var dest_dir = std.fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| {
26 std.log.err("unable to open destination directory '{s}': {s}", .{
27 dest_dir_path, @errorName(err),
28 });
30 var dest_dir = Io.Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| {
31 std.log.err("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err });
2932 std.process.exit(1);
3033 };
31 defer dest_dir.close();
34 defer dest_dir.close(io);
3235
33 var freebsd_src_dir = try std.fs.cwd().openDir(freebsd_src_path, .{});
34 defer freebsd_src_dir.close();
36 var freebsd_src_dir = try Io.Dir.cwd().openDir(io, freebsd_src_path, .{});
37 defer freebsd_src_dir.close(io);
3538
3639 // Copy updated files from upstream.
3740 {
3841 var walker = try dest_dir.walk(arena);
3942 defer walker.deinit();
4043
41 walk: while (try walker.next()) |entry| {
44 walk: while (try walker.next(io)) |entry| {
4245 if (entry.kind != .file) continue;
4346 if (std.mem.startsWith(u8, entry.basename, ".")) continue;
4447 for (exempt_files) |p| {
......@@ -46,18 +49,15 @@ pub fn main() !void {
4649 }
4750
4851 std.log.info("updating '{s}/{s}' from '{s}/{s}'", .{
49 dest_dir_path, entry.path,
50 freebsd_src_path, entry.path,
52 dest_dir_path, entry.path, freebsd_src_path, entry.path,
5153 });
5254
53 freebsd_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| {
54 std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{
55 freebsd_src_path, entry.path,
56 dest_dir_path, entry.path,
57 @errorName(err),
55 freebsd_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| {
56 std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{
57 freebsd_src_path, entry.path, dest_dir_path, entry.path, err,
5858 });
5959 if (err == error.FileNotFound) {
60 try dest_dir.deleteFile(entry.path);
60 try dest_dir.deleteFile(io, entry.path);
6161 }
6262 };
6363 }
tools/update_glibc.zig+28-36
......@@ -7,9 +7,11 @@
77//! `zig run ../tools/update_glibc.zig -- ~/Downloads/glibc ..`
88
99const std = @import("std");
10const Io = std.Io;
11const Dir = std.Io.Dir;
1012const mem = std.mem;
1113const log = std.log;
12const fs = std.fs;
14const fatal = std.process.fatal;
1315
1416const exempt_files = [_][]const u8{
1517 // This file is maintained by a separate project and does not come from glibc.
......@@ -41,28 +43,30 @@ pub fn main() !void {
4143 defer arena_instance.deinit();
4244 const arena = arena_instance.allocator();
4345
46 var threaded: Io.Threaded = .init(arena, .{});
47 defer threaded.deinit();
48 const io = threaded.io();
49
4450 const args = try std.process.argsAlloc(arena);
4551 const glibc_src_path = args[1];
4652 const zig_src_path = args[2];
4753
4854 const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/glibc", .{zig_src_path});
4955
50 var dest_dir = fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| {
51 fatal("unable to open destination directory '{s}': {s}", .{
52 dest_dir_path, @errorName(err),
53 });
56 var dest_dir = Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| {
57 fatal("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err });
5458 };
55 defer dest_dir.close();
59 defer dest_dir.close(io);
5660
57 var glibc_src_dir = try fs.cwd().openDir(glibc_src_path, .{});
58 defer glibc_src_dir.close();
61 var glibc_src_dir = try Dir.cwd().openDir(io, glibc_src_path, .{});
62 defer glibc_src_dir.close(io);
5963
6064 // Copy updated files from upstream.
6165 {
6266 var walker = try dest_dir.walk(arena);
6367 defer walker.deinit();
6468
65 walk: while (try walker.next()) |entry| {
69 walk: while (try walker.next(io)) |entry| {
6670 if (entry.kind != .file) continue;
6771 if (mem.startsWith(u8, entry.basename, ".")) continue;
6872 for (exempt_files) |p| {
......@@ -72,14 +76,12 @@ pub fn main() !void {
7276 if (mem.endsWith(u8, entry.path, ext)) continue :walk;
7377 }
7478
75 glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| {
76 log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{
77 glibc_src_path, entry.path,
78 dest_dir_path, entry.path,
79 @errorName(err),
79 glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| {
80 log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{
81 glibc_src_path, entry.path, dest_dir_path, entry.path, err,
8082 });
8183 if (err == error.FileNotFound) {
82 try dest_dir.deleteFile(entry.path);
84 try dest_dir.deleteFile(io, entry.path);
8385 }
8486 };
8587 }
......@@ -88,25 +90,23 @@ pub fn main() !void {
8890 // Warn about duplicated files inside glibc/include/* that can be omitted
8991 // because they are already in generic-glibc/*.
9092
91 var include_dir = dest_dir.openDir("include", .{ .iterate = true }) catch |err| {
92 fatal("unable to open directory '{s}/include': {s}", .{
93 dest_dir_path, @errorName(err),
94 });
93 var include_dir = dest_dir.openDir(io, "include", .{ .iterate = true }) catch |err| {
94 fatal("unable to open directory '{s}/include': {t}", .{ dest_dir_path, err });
9595 };
96 defer include_dir.close();
96 defer include_dir.close(io);
9797
9898 const generic_glibc_path = try std.fmt.allocPrint(
9999 arena,
100100 "{s}/lib/libc/include/generic-glibc",
101101 .{zig_src_path},
102102 );
103 var generic_glibc_dir = try fs.cwd().openDir(generic_glibc_path, .{});
104 defer generic_glibc_dir.close();
103 var generic_glibc_dir = try Dir.cwd().openDir(io, generic_glibc_path, .{});
104 defer generic_glibc_dir.close(io);
105105
106106 var walker = try include_dir.walk(arena);
107107 defer walker.deinit();
108108
109 walk: while (try walker.next()) |entry| {
109 walk: while (try walker.next(io)) |entry| {
110110 if (entry.kind != .file) continue;
111111 if (mem.startsWith(u8, entry.basename, ".")) continue;
112112 for (exempt_files) |p| {
......@@ -116,23 +116,21 @@ pub fn main() !void {
116116 const max_file_size = 10 * 1024 * 1024;
117117
118118 const generic_glibc_contents = generic_glibc_dir.readFileAlloc(
119 io,
119120 entry.path,
120121 arena,
121122 .limited(max_file_size),
122123 ) catch |err| switch (err) {
123124 error.FileNotFound => continue,
124 else => |e| fatal("unable to load '{s}/include/{s}': {s}", .{
125 generic_glibc_path, entry.path, @errorName(e),
126 }),
125 else => |e| fatal("unable to load '{s}/include/{s}': {t}", .{ generic_glibc_path, entry.path, e }),
127126 };
128127 const glibc_include_contents = include_dir.readFileAlloc(
128 io,
129129 entry.path,
130130 arena,
131131 .limited(max_file_size),
132132 ) catch |err| {
133 fatal("unable to load '{s}/include/{s}': {s}", .{
134 dest_dir_path, entry.path, @errorName(err),
135 });
133 fatal("unable to load '{s}/include/{s}': {t}", .{ dest_dir_path, entry.path, err });
136134 };
137135
138136 const whitespace = " \r\n\t";
......@@ -140,14 +138,8 @@ pub fn main() !void {
140138 const glibc_include_trimmed = mem.trim(u8, glibc_include_contents, whitespace);
141139 if (mem.eql(u8, generic_glibc_trimmed, glibc_include_trimmed)) {
142140 log.warn("same contents: '{s}/include/{s}' and '{s}/include/{s}'", .{
143 generic_glibc_path, entry.path,
144 dest_dir_path, entry.path,
141 generic_glibc_path, entry.path, dest_dir_path, entry.path,
145142 });
146143 }
147144 }
148145}
149
150fn fatal(comptime format: []const u8, args: anytype) noreturn {
151 log.err(format, args);
152 std.process.exit(1);
153}
tools/update_mingw.zig+35-29
......@@ -1,35 +1,41 @@
11const std = @import("std");
2const Io = std.Io;
3const Dir = std.Io.Dir;
24
35pub fn main() !void {
46 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
57 defer arena_instance.deinit();
68 const arena = arena_instance.allocator();
79
10 var threaded: Io.Threaded = .init(arena, .{});
11 defer threaded.deinit();
12 const io = threaded.io();
13
814 const args = try std.process.argsAlloc(arena);
915 const zig_src_lib_path = args[1];
1016 const mingw_src_path = args[2];
1117
12 const dest_mingw_crt_path = try std.fs.path.join(arena, &.{
18 const dest_mingw_crt_path = try Dir.path.join(arena, &.{
1319 zig_src_lib_path, "libc", "mingw",
1420 });
15 const src_mingw_crt_path = try std.fs.path.join(arena, &.{
21 const src_mingw_crt_path = try Dir.path.join(arena, &.{
1622 mingw_src_path, "mingw-w64-crt",
1723 });
1824
1925 // Update only the set of existing files we have already chosen to include
2026 // in zig's installation.
2127
22 var dest_crt_dir = std.fs.cwd().openDir(dest_mingw_crt_path, .{ .iterate = true }) catch |err| {
23 std.log.err("unable to open directory '{s}': {s}", .{ dest_mingw_crt_path, @errorName(err) });
28 var dest_crt_dir = Dir.cwd().openDir(io, dest_mingw_crt_path, .{ .iterate = true }) catch |err| {
29 std.log.err("unable to open directory '{s}': {t}", .{ dest_mingw_crt_path, err });
2430 std.process.exit(1);
2531 };
26 defer dest_crt_dir.close();
32 defer dest_crt_dir.close(io);
2733
28 var src_crt_dir = std.fs.cwd().openDir(src_mingw_crt_path, .{ .iterate = true }) catch |err| {
29 std.log.err("unable to open directory '{s}': {s}", .{ src_mingw_crt_path, @errorName(err) });
34 var src_crt_dir = Dir.cwd().openDir(io, src_mingw_crt_path, .{ .iterate = true }) catch |err| {
35 std.log.err("unable to open directory '{s}': {t}", .{ src_mingw_crt_path, err });
3036 std.process.exit(1);
3137 };
32 defer src_crt_dir.close();
38 defer src_crt_dir.close(io);
3339
3440 {
3541 var walker = try dest_crt_dir.walk(arena);
......@@ -37,10 +43,10 @@ pub fn main() !void {
3743
3844 var fail = false;
3945
40 while (try walker.next()) |entry| {
46 while (try walker.next(io)) |entry| {
4147 if (entry.kind != .file) continue;
4248
43 src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, .{}) catch |err| switch (err) {
49 src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, io, .{}) catch |err| switch (err) {
4450 error.FileNotFound => {
4551 const keep = for (kept_crt_files) |item| {
4652 if (std.mem.eql(u8, entry.path, item)) break true;
......@@ -49,11 +55,11 @@ pub fn main() !void {
4955
5056 if (!keep) {
5157 std.log.warn("deleting {s}", .{entry.path});
52 try dest_crt_dir.deleteFile(entry.path);
58 try dest_crt_dir.deleteFile(io, entry.path);
5359 }
5460 },
5561 else => {
56 std.log.err("unable to copy {s}: {s}", .{ entry.path, @errorName(err) });
62 std.log.err("unable to copy {s}: {t}", .{ entry.path, err });
5763 fail = true;
5864 },
5965 };
......@@ -63,24 +69,24 @@ pub fn main() !void {
6369 }
6470
6571 {
66 const dest_mingw_winpthreads_path = try std.fs.path.join(arena, &.{
72 const dest_mingw_winpthreads_path = try Dir.path.join(arena, &.{
6773 zig_src_lib_path, "libc", "mingw", "winpthreads",
6874 });
69 const src_mingw_libraries_winpthreads_src_path = try std.fs.path.join(arena, &.{
75 const src_mingw_libraries_winpthreads_src_path = try Dir.path.join(arena, &.{
7076 mingw_src_path, "mingw-w64-libraries", "winpthreads", "src",
7177 });
7278
73 var dest_winpthreads_dir = std.fs.cwd().openDir(dest_mingw_winpthreads_path, .{ .iterate = true }) catch |err| {
79 var dest_winpthreads_dir = Dir.cwd().openDir(io, dest_mingw_winpthreads_path, .{ .iterate = true }) catch |err| {
7480 std.log.err("unable to open directory '{s}': {s}", .{ dest_mingw_winpthreads_path, @errorName(err) });
7581 std.process.exit(1);
7682 };
77 defer dest_winpthreads_dir.close();
83 defer dest_winpthreads_dir.close(io);
7884
79 var src_winpthreads_dir = std.fs.cwd().openDir(src_mingw_libraries_winpthreads_src_path, .{ .iterate = true }) catch |err| {
85 var src_winpthreads_dir = Dir.cwd().openDir(io, src_mingw_libraries_winpthreads_src_path, .{ .iterate = true }) catch |err| {
8086 std.log.err("unable to open directory '{s}': {s}", .{ src_mingw_libraries_winpthreads_src_path, @errorName(err) });
8187 std.process.exit(1);
8288 };
83 defer src_winpthreads_dir.close();
89 defer src_winpthreads_dir.close(io);
8490
8591 {
8692 var walker = try dest_winpthreads_dir.walk(arena);
......@@ -88,16 +94,16 @@ pub fn main() !void {
8894
8995 var fail = false;
9096
91 while (try walker.next()) |entry| {
97 while (try walker.next(io)) |entry| {
9298 if (entry.kind != .file) continue;
9399
94 src_winpthreads_dir.copyFile(entry.path, dest_winpthreads_dir, entry.path, .{}) catch |err| switch (err) {
100 src_winpthreads_dir.copyFile(entry.path, dest_winpthreads_dir, entry.path, io, .{}) catch |err| switch (err) {
95101 error.FileNotFound => {
96102 std.log.warn("deleting {s}", .{entry.path});
97 try dest_winpthreads_dir.deleteFile(entry.path);
103 try dest_winpthreads_dir.deleteFile(io, entry.path);
98104 },
99105 else => {
100 std.log.err("unable to copy {s}: {s}", .{ entry.path, @errorName(err) });
106 std.log.err("unable to copy {s}: {t}", .{ entry.path, err });
101107 fail = true;
102108 },
103109 };
......@@ -114,17 +120,17 @@ pub fn main() !void {
114120
115121 var fail = false;
116122
117 while (try walker.next()) |entry| {
123 while (try walker.next(io)) |entry| {
118124 switch (entry.kind) {
119125 .directory => {
120126 switch (entry.depth()) {
121127 1 => if (def_dirs.has(entry.basename)) {
122 try walker.enter(entry);
128 try walker.enter(io, entry);
123129 continue;
124130 },
125131 else => {
126132 // The top-level directory was already validated
127 try walker.enter(entry);
133 try walker.enter(io, entry);
128134 continue;
129135 },
130136 }
......@@ -151,20 +157,20 @@ pub fn main() !void {
151157 if (std.mem.endsWith(u8, entry.basename, "_onecore.def"))
152158 continue;
153159
154 src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, .{}) catch |err| {
155 std.log.err("unable to copy {s}: {s}", .{ entry.path, @errorName(err) });
160 src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, io, .{}) catch |err| {
161 std.log.err("unable to copy {s}: {t}", .{ entry.path, err });
156162 fail = true;
157163 };
158164 }
159165 if (fail) std.process.exit(1);
160166 }
161167
162 return std.process.cleanExit();
168 return std.process.cleanExit(io);
163169}
164170
165171const kept_crt_files = [_][]const u8{
166172 "COPYING",
167 "include" ++ std.fs.path.sep_str ++ "config.h",
173 "include" ++ Dir.path.sep_str ++ "config.h",
168174};
169175
170176const def_exts = [_][]const u8{
tools/update_netbsd_libc.zig+15-14
......@@ -5,6 +5,7 @@
55//! `zig run tools/update_netbsd_libc.zig -- ~/Downloads/netbsd-src .`
66
77const std = @import("std");
8const Io = std.Io;
89
910const exempt_files = [_][]const u8{
1011 // This file is maintained by a separate project and does not come from NetBSD.
......@@ -16,29 +17,31 @@ pub fn main() !void {
1617 defer arena_instance.deinit();
1718 const arena = arena_instance.allocator();
1819
20 var threaded: Io.Threaded = .init(arena, .{});
21 defer threaded.deinit();
22 const io = threaded.io();
23
1924 const args = try std.process.argsAlloc(arena);
2025 const netbsd_src_path = args[1];
2126 const zig_src_path = args[2];
2227
2328 const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/netbsd", .{zig_src_path});
2429
25 var dest_dir = std.fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| {
26 std.log.err("unable to open destination directory '{s}': {s}", .{
27 dest_dir_path, @errorName(err),
28 });
30 var dest_dir = Io.Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| {
31 std.log.err("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err });
2932 std.process.exit(1);
3033 };
31 defer dest_dir.close();
34 defer dest_dir.close(io);
3235
33 var netbsd_src_dir = try std.fs.cwd().openDir(netbsd_src_path, .{});
34 defer netbsd_src_dir.close();
36 var netbsd_src_dir = try Io.Dir.cwd().openDir(io, netbsd_src_path, .{});
37 defer netbsd_src_dir.close(io);
3538
3639 // Copy updated files from upstream.
3740 {
3841 var walker = try dest_dir.walk(arena);
3942 defer walker.deinit();
4043
41 walk: while (try walker.next()) |entry| {
44 walk: while (try walker.next(io)) |entry| {
4245 if (entry.kind != .file) continue;
4346 if (std.mem.startsWith(u8, entry.basename, ".")) continue;
4447 for (exempt_files) |p| {
......@@ -50,14 +53,12 @@ pub fn main() !void {
5053 netbsd_src_path, entry.path,
5154 });
5255
53 netbsd_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| {
54 std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{
55 netbsd_src_path, entry.path,
56 dest_dir_path, entry.path,
57 @errorName(err),
56 netbsd_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| {
57 std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{
58 netbsd_src_path, entry.path, dest_dir_path, entry.path, err,
5859 });
5960 if (err == error.FileNotFound) {
60 try dest_dir.deleteFile(entry.path);
61 try dest_dir.deleteFile(io, entry.path);
6162 }
6263 };
6364 }