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...@@ -436,9 +436,6 @@ set(ZIG_STAGE2_SOURCES
436 lib/std/fmt.zig436 lib/std/fmt.zig
437 lib/std/fmt/parse_float.zig437 lib/std/fmt/parse_float.zig
438 lib/std/fs.zig438 lib/std/fs.zig
439 lib/std/fs/AtomicFile.zig
440 lib/std/fs/Dir.zig
441 lib/std/fs/File.zig
442 lib/std/fs/get_app_data_dir.zig439 lib/std/fs/get_app_data_dir.zig
443 lib/std/fs/path.zig440 lib/std/fs/path.zig
444 lib/std/hash.zig441 lib/std/hash.zig
build.zig+27-68
...@@ -1,18 +1,20 @@...@@ -1,18 +1,20 @@
1const std = @import("std");1const std = @import("std");
2const builtin = std.builtin;2const builtin = std.builtin;
3const tests = @import("test/tests.zig");
4const BufMap = std.BufMap;3const BufMap = std.BufMap;
5const mem = std.mem;4const mem = std.mem;
6const io = std.io;
7const fs = std.fs;5const fs = std.fs;
8const InstallDirectoryOptions = std.Build.InstallDirectoryOptions;6const InstallDirectoryOptions = std.Build.InstallDirectoryOptions;
9const assert = std.debug.assert;7const assert = std.debug.assert;
8const Io = std.Io;
9
10const tests = @import("test/tests.zig");
10const DevEnv = @import("src/dev.zig").Env;11const DevEnv = @import("src/dev.zig").Env;
11const ValueInterpretMode = enum { direct, by_name };
1212
13const zig_version: std.SemanticVersion = .{ .major = 0, .minor = 16, .patch = 0 };13const zig_version: std.SemanticVersion = .{ .major = 0, .minor = 16, .patch = 0 };
14const stack_size = 46 * 1024 * 1024;14const stack_size = 46 * 1024 * 1024;
1515
16const ValueInterpretMode = enum { direct, by_name };
17
16pub fn build(b: *std.Build) !void {18pub fn build(b: *std.Build) !void {
17 const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false;19 const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false;
18 const target = b.standardTargetOptions(.{20 const target = b.standardTargetOptions(.{
...@@ -306,8 +308,10 @@ pub fn build(b: *std.Build) !void {...@@ -306,8 +308,10 @@ pub fn build(b: *std.Build) !void {
306308
307 if (enable_llvm) {309 if (enable_llvm) {
308 const cmake_cfg = if (static_llvm) null else blk: {310 const cmake_cfg = if (static_llvm) null else blk: {
311 const io = b.graph.io;
312 const cwd: Io.Dir = .cwd();
309 if (findConfigH(b, config_h_path_option)) |config_h_path| {313 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;
311 break :blk parseConfigH(b, file_contents);315 break :blk parseConfigH(b, file_contents);
312 } else {316 } else {
313 std.log.warn("config.h could not be located automatically. Consider providing it explicitly via \"-Dconfig_h\"", .{});317 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 {...@@ -531,10 +535,6 @@ pub fn build(b: *std.Build) !void {
531 .aarch64 => 701_413_785,535 .aarch64 => 701_413_785,
532 else => 800_000_000,536 else => 800_000_000,
533 },537 },
534 .windows => switch (b.graph.host.result.cpu.arch) {
535 .x86_64 => 536_414_208,
536 else => 600_000_000,
537 },
538 else => 900_000_000,538 else => 900_000_000,
539 },539 },
540 }));540 }));
...@@ -561,30 +561,7 @@ pub fn build(b: *std.Build) !void {...@@ -561,30 +561,7 @@ pub fn build(b: *std.Build) !void {
561 .skip_llvm = skip_llvm,561 .skip_llvm = skip_llvm,
562 .skip_libc = true,562 .skip_libc = true,
563 .no_builtin = true,563 .no_builtin = true,
564 .max_rss = switch (b.graph.host.result.os.tag) {564 .max_rss = 900_000_000,
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 },
588 }));565 }));
589566
590 test_modules_step.dependOn(tests.addModuleTests(b, .{567 test_modules_step.dependOn(tests.addModuleTests(b, .{
...@@ -647,30 +624,7 @@ pub fn build(b: *std.Build) !void {...@@ -647,30 +624,7 @@ pub fn build(b: *std.Build) !void {
647 .use_llvm = use_llvm,624 .use_llvm = use_llvm,
648 .use_lld = use_llvm,625 .use_lld = use_llvm,
649 .zig_lib_dir = b.path("lib"),626 .zig_lib_dir = b.path("lib"),
650 .max_rss = switch (b.graph.host.result.os.tag) {627 .max_rss = 2_500_000_000,
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 },
674 });628 });
675 if (link_libc) {629 if (link_libc) {
676 unit_tests.root_module.link_libc = true;630 unit_tests.root_module.link_libc = true;
...@@ -762,7 +716,7 @@ pub fn build(b: *std.Build) !void {...@@ -762,7 +716,7 @@ pub fn build(b: *std.Build) !void {
762 }716 }
763717
764 const test_incremental_step = b.step("test-incremental", "Run the incremental compilation test cases");718 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);
766 if (!skip_test_incremental) test_step.dependOn(test_incremental_step);720 if (!skip_test_incremental) test_step.dependOn(test_incremental_step);
767721
768 if (tests.addLibcTests(b, .{722 if (tests.addLibcTests(b, .{
...@@ -1153,10 +1107,13 @@ const CMakeConfig = struct {...@@ -1153,10 +1107,13 @@ const CMakeConfig = struct {
1153const max_config_h_bytes = 1 * 1024 * 1024;1107const max_config_h_bytes = 1 * 1024 * 1024;
11541108
1155fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {1109fn 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
1156 if (config_h_path_option) |path| {1113 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, .{});
1158 if (config_h_or_err) |*file| {1115 if (config_h_or_err) |*file| {
1159 file.close();1116 file.close(io);
1160 return path;1117 return path;
1161 } else |_| {1118 } else |_| {
1162 std.log.err("Could not open provided config.h: \"{s}\"", .{path});1119 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 {...@@ -1166,13 +1123,13 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
11661123
1167 var check_dir = fs.path.dirname(b.graph.zig_exe).?;1124 var check_dir = fs.path.dirname(b.graph.zig_exe).?;
1168 while (true) {1125 while (true) {
1169 var dir = fs.cwd().openDir(check_dir, .{}) catch unreachable;1126 var dir = cwd.openDir(io, check_dir, .{}) catch unreachable;
1170 defer dir.close();1127 defer dir.close(io);
11711128
1172 // Check if config.h is present in dir1129 // 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", .{});
1174 if (config_h_or_err) |*file| {1131 if (config_h_or_err) |*file| {
1175 file.close();1132 file.close(io);
1176 return fs.path.join(1133 return fs.path.join(
1177 b.allocator,1134 b.allocator,
1178 &[_][]const u8{ check_dir, "config.h" },1135 &[_][]const u8{ check_dir, "config.h" },
...@@ -1183,9 +1140,9 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {...@@ -1183,9 +1140,9 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
1183 }1140 }
11841141
1185 // Check if we reached the source root by looking for .git, and bail if so1142 // 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", .{});
1187 if (git_dir_or_err) |*git_dir| {1144 if (git_dir_or_err) |*git_dir| {
1188 git_dir.close();1145 git_dir.close(io);
1189 return null;1146 return null;
1190 } else |_| {}1147 } else |_| {}
11911148
...@@ -1581,6 +1538,8 @@ const llvm_libs_xtensa = [_][]const u8{...@@ -1581,6 +1538,8 @@ const llvm_libs_xtensa = [_][]const u8{
1581};1538};
15821539
1583fn generateLangRef(b: *std.Build) std.Build.LazyPath {1540fn generateLangRef(b: *std.Build) std.Build.LazyPath {
1541 const io = b.graph.io;
1542
1584 const doctest_exe = b.addExecutable(.{1543 const doctest_exe = b.addExecutable(.{
1585 .name = "doctest",1544 .name = "doctest",
1586 .root_module = b.createModule(.{1545 .root_module = b.createModule(.{
...@@ -1590,17 +1549,17 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {...@@ -1590,17 +1549,17 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
1590 }),1549 }),
1591 });1550 });
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| {
1594 std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{1553 std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{
1595 b.build_root, @errorName(err),1554 b.build_root, @errorName(err),
1596 });1555 });
1597 };1556 };
1598 defer dir.close();1557 defer dir.close(io);
15991558
1600 var wf = b.addWriteFiles();1559 var wf = b.addWriteFiles();
16011560
1602 var it = dir.iterateAssumeFirstIteration();1561 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| {
1604 if (std.mem.startsWith(u8, entry.name, ".") or entry.kind != .file)1563 if (std.mem.startsWith(u8, entry.name, ".") or entry.kind != .file)
1605 continue;1564 continue;
16061565
doc/langref/bad_default_value.zig+1-1
...@@ -17,7 +17,7 @@ pub fn main() !void {...@@ -17,7 +17,7 @@ pub fn main() !void {
17 .maximum = 0.20,17 .maximum = 0.20,
18 };18 };
19 const category = threshold.categorize(0.90);19 const category = threshold.categorize(0.90);
20 try std.fs.File.stdout().writeAll(@tagName(category));20 std.log.info("category: {t}", .{category});
21}21}
2222
23const std = @import("std");23const std = @import("std");
doc/langref/hello.zig+11-1
...@@ -1,7 +1,17 @@...@@ -1,7 +1,17 @@
1const std = @import("std");1const std = @import("std");
22
3// See https://github.com/ziglang/zig/issues/24510
4// for the plan to simplify this code.
3pub fn main() !void {5pub 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");
5}15}
616
7// exe=succeed17// exe=succeed
lib/compiler/aro/aro/Compilation.zig+21-15
...@@ -154,7 +154,7 @@ gpa: Allocator,...@@ -154,7 +154,7 @@ gpa: Allocator,
154/// Allocations in this arena live all the way until `Compilation.deinit`.154/// Allocations in this arena live all the way until `Compilation.deinit`.
155arena: Allocator,155arena: Allocator,
156io: Io,156io: Io,
157cwd: std.fs.Dir,157cwd: Io.Dir,
158diagnostics: *Diagnostics,158diagnostics: *Diagnostics,
159159
160sources: std.StringArrayHashMapUnmanaged(Source) = .empty,160sources: std.StringArrayHashMapUnmanaged(Source) = .empty,
...@@ -181,7 +181,7 @@ pragma_handlers: std.StringArrayHashMapUnmanaged(*Pragma) = .empty,...@@ -181,7 +181,7 @@ pragma_handlers: std.StringArrayHashMapUnmanaged(*Pragma) = .empty,
181/// Used by MS extensions which allow searching for includes relative to the directory of the main source file.181/// Used by MS extensions which allow searching for includes relative to the directory of the main source file.
182ms_cwd_source_id: ?Source.Id = null,182ms_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 {
185 return .{185 return .{
186 .gpa = gpa,186 .gpa = gpa,
187 .arena = arena,187 .arena = arena,
...@@ -193,7 +193,7 @@ pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics,...@@ -193,7 +193,7 @@ pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics,
193193
194/// Initialize Compilation with default environment,194/// Initialize Compilation with default environment,
195/// pragma handlers and emulation mode set to target.195/// 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 {
197 var comp: Compilation = .{197 var comp: Compilation = .{
198 .gpa = gpa,198 .gpa = gpa,
199 .arena = arena,199 .arena = arena,
...@@ -1639,12 +1639,14 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin...@@ -1639,12 +1639,14 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin
1639 return error.FileNotFound;1639 return error.FileNotFound;
1640 }1640 }
16411641
1642 const file = try comp.cwd.openFile(path, .{});1642 const io = comp.io;
1643 defer file.close();1643
1644 const file = try comp.cwd.openFile(io, path, .{});
1645 defer file.close(io);
1644 return comp.addSourceFromFile(file, path, kind);1646 return comp.addSourceFromFile(file, path, kind);
1645}1647}
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 {
1648 const contents = try comp.getFileContents(file, .unlimited);1650 const contents = try comp.getFileContents(file, .unlimited);
1649 errdefer comp.gpa.free(contents);1651 errdefer comp.gpa.free(contents);
1650 return comp.addSourceFromOwnedBuffer(path, contents, kind);1652 return comp.addSourceFromOwnedBuffer(path, contents, kind);
...@@ -1711,7 +1713,8 @@ pub fn initSearchPath(comp: *Compilation, includes: []const Include, verbose: bo...@@ -1711,7 +1713,8 @@ pub fn initSearchPath(comp: *Compilation, includes: []const Include, verbose: bo
1711 }1713 }
1712}1714}
1713fn addToSearchPath(comp: *Compilation, include: Include, verbose: bool) !void {1715fn 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 {
1715 if (verbose) {1718 if (verbose) {
1716 std.debug.print("ignoring nonexistent directory \"{s}\"\n", .{include.path});1719 std.debug.print("ignoring nonexistent directory \"{s}\"\n", .{include.path});
1717 return;1720 return;
...@@ -1971,12 +1974,14 @@ fn getPathContents(comp: *Compilation, path: []const u8, limit: Io.Limit) ![]u8...@@ -1971,12 +1974,14 @@ fn getPathContents(comp: *Compilation, path: []const u8, limit: Io.Limit) ![]u8
1971 return error.FileNotFound;1974 return error.FileNotFound;
1972 }1975 }
19731976
1974 const file = try comp.cwd.openFile(path, .{});1977 const io = comp.io;
1975 defer file.close();1978
1979 const file = try comp.cwd.openFile(io, path, .{});
1980 defer file.close(io);
1976 return comp.getFileContents(file, limit);1981 return comp.getFileContents(file, limit);
1977}1982}
19781983
1979fn getFileContents(comp: *Compilation, file: std.fs.File, limit: Io.Limit) ![]u8 {1984fn getFileContents(comp: *Compilation, file: Io.File, limit: Io.Limit) ![]u8 {
1980 var file_buf: [4096]u8 = undefined;1985 var file_buf: [4096]u8 = undefined;
1981 var file_reader = file.reader(comp.io, &file_buf);1986 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 {...@@ -2158,8 +2163,9 @@ pub fn locSlice(comp: *const Compilation, loc: Source.Location) []const u8 {
2158}2163}
21592164
2160pub fn getSourceMTimeUncached(comp: *const Compilation, source_id: Source.Id) ?u64 {2165pub fn getSourceMTimeUncached(comp: *const Compilation, source_id: Source.Id) ?u64 {
2166 const io = comp.io;
2161 const source = comp.getSource(source_id);2167 const source = comp.getSource(source_id);
2162 if (comp.cwd.statFile(source.path)) |stat| {2168 if (comp.cwd.statFile(io, source.path, .{})) |stat| {
2163 return std.math.cast(u64, stat.mtime.toSeconds());2169 return std.math.cast(u64, stat.mtime.toSeconds());
2164 } else |_| {2170 } else |_| {
2165 return null;2171 return null;
...@@ -2249,7 +2255,7 @@ test "addSourceFromBuffer" {...@@ -2249,7 +2255,7 @@ test "addSourceFromBuffer" {
2249 var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);2255 var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
2250 defer arena.deinit();2256 defer arena.deinit();
2251 var diagnostics: Diagnostics = .{ .output = .ignore };2257 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());
2253 defer comp.deinit();2259 defer comp.deinit();
22542260
2255 const source = try comp.addSourceFromBuffer("path", str);2261 const source = try comp.addSourceFromBuffer("path", str);
...@@ -2263,7 +2269,7 @@ test "addSourceFromBuffer" {...@@ -2263,7 +2269,7 @@ test "addSourceFromBuffer" {
2263 var arena: std.heap.ArenaAllocator = .init(allocator);2269 var arena: std.heap.ArenaAllocator = .init(allocator);
2264 defer arena.deinit();2270 defer arena.deinit();
2265 var diagnostics: Diagnostics = .{ .output = .ignore };2271 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());
2267 defer comp.deinit();2273 defer comp.deinit();
22682274
2269 _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n");2275 _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n");
...@@ -2309,7 +2315,7 @@ test "addSourceFromBuffer - exhaustive check for carriage return elimination" {...@@ -2309,7 +2315,7 @@ test "addSourceFromBuffer - exhaustive check for carriage return elimination" {
2309 var buf: [alphabet.len]u8 = @splat(alphabet[0]);2315 var buf: [alphabet.len]u8 = @splat(alphabet[0]);
23102316
2311 var diagnostics: Diagnostics = .{ .output = .ignore };2317 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());
2313 defer comp.deinit();2319 defer comp.deinit();
23142320
2315 var source_count: u32 = 0;2321 var source_count: u32 = 0;
...@@ -2337,7 +2343,7 @@ test "ignore BOM at beginning of file" {...@@ -2337,7 +2343,7 @@ test "ignore BOM at beginning of file" {
2337 const Test = struct {2343 const Test = struct {
2338 fn run(arena: Allocator, buf: []const u8) !void {2344 fn run(arena: Allocator, buf: []const u8) !void {
2339 var diagnostics: Diagnostics = .{ .output = .ignore };2345 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());
2341 defer comp.deinit();2347 defer comp.deinit();
23422348
2343 const source = try comp.addSourceFromBuffer("file.c", buf);2349 const source = try comp.addSourceFromBuffer("file.c", buf);
lib/compiler/aro/aro/Diagnostics.zig+18-20
...@@ -24,20 +24,21 @@ pub const Message = struct {...@@ -24,20 +24,21 @@ pub const Message = struct {
24 @"fatal error",24 @"fatal error",
25 };25 };
2626
27 pub fn write(msg: Message, w: *std.Io.Writer, config: std.Io.tty.Config, details: bool) std.Io.tty.Config.SetColorError!void {27 pub fn write(msg: Message, t: std.Io.Terminal, details: bool) std.Io.Terminal.SetColorError!void {
28 try config.setColor(w, .bold);28 const w = t.writer;
29 try t.setColor(.bold);
29 if (msg.location) |loc| {30 if (msg.location) |loc| {
30 try w.print("{s}:{d}:{d}: ", .{ loc.path, loc.line_no, loc.col });31 try w.print("{s}:{d}:{d}: ", .{ loc.path, loc.line_no, loc.col });
31 }32 }
32 switch (msg.effective_kind) {33 switch (msg.effective_kind) {
33 .@"fatal error", .@"error" => try config.setColor(w, .bright_red),34 .@"fatal error", .@"error" => try t.setColor(.bright_red),
34 .note => try config.setColor(w, .bright_cyan),35 .note => try t.setColor(.bright_cyan),
35 .warning => try config.setColor(w, .bright_magenta),36 .warning => try t.setColor(.bright_magenta),
36 .off => unreachable,37 .off => unreachable,
37 }38 }
38 try w.print("{s}: ", .{@tagName(msg.effective_kind)});39 try w.print("{s}: ", .{@tagName(msg.effective_kind)});
3940
40 try config.setColor(w, .white);41 try t.setColor(.white);
41 try w.writeAll(msg.text);42 try w.writeAll(msg.text);
42 if (msg.opt) |some| {43 if (msg.opt) |some| {
43 if (msg.effective_kind == .@"error" and msg.kind != .@"error") {44 if (msg.effective_kind == .@"error" and msg.kind != .@"error") {
...@@ -55,17 +56,17 @@ pub const Message = struct {...@@ -55,17 +56,17 @@ pub const Message = struct {
5556
56 if (!details or msg.location == null) {57 if (!details or msg.location == null) {
57 try w.writeAll("\n");58 try w.writeAll("\n");
58 try config.setColor(w, .reset);59 try t.setColor(.reset);
59 } else {60 } else {
60 const loc = msg.location.?;61 const loc = msg.location.?;
61 const trailer = if (loc.end_with_splice) "\\ " else "";62 const trailer = if (loc.end_with_splice) "\\ " else "";
62 try config.setColor(w, .reset);63 try t.setColor(.reset);
63 try w.print("\n{s}{s}\n", .{ loc.line, trailer });64 try w.print("\n{s}{s}\n", .{ loc.line, trailer });
64 try w.splatByteAll(' ', loc.width);65 try w.splatByteAll(' ', loc.width);
65 try config.setColor(w, .bold);66 try t.setColor(.bold);
66 try config.setColor(w, .bright_green);67 try t.setColor(.bright_green);
67 try w.writeAll("^\n");68 try w.writeAll("^\n");
68 try config.setColor(w, .reset);69 try t.setColor(.reset);
69 }70 }
70 try w.flush();71 try w.flush();
71 }72 }
...@@ -290,10 +291,7 @@ pub const State = struct {...@@ -290,10 +291,7 @@ pub const State = struct {
290const Diagnostics = @This();291const Diagnostics = @This();
291292
292output: union(enum) {293output: union(enum) {
293 to_writer: struct {294 to_writer: std.Io.Terminal,
294 writer: *std.Io.Writer,
295 color: std.Io.tty.Config,
296 },
297 to_list: struct {295 to_list: struct {
298 messages: std.ArrayList(Message) = .empty,296 messages: std.ArrayList(Message) = .empty,
299 arena: std.heap.ArenaAllocator,297 arena: std.heap.ArenaAllocator,
...@@ -543,11 +541,11 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void {...@@ -543,11 +541,11 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void {
543541
544 switch (d.output) {542 switch (d.output) {
545 .ignore => {},543 .ignore => {},
546 .to_writer => |writer| {544 .to_writer => |t| {
547 var config = writer.color;545 var new_mode = t.mode;
548 if (d.color == false) config = .no_color;546 if (d.color == false) new_mode = .no_color;
549 if (d.color == true and config == .no_color) config = .escape_codes;547 if (d.color == true and new_mode == .no_color) new_mode = .escape_codes;
550 msg.write(writer.writer, config, d.details) catch {548 msg.write(.{ .writer = t.writer, .mode = new_mode }, d.details) catch {
551 return error.FatalError;549 return error.FatalError;
552 };550 };
553 },551 },
lib/compiler/aro/aro/Driver.zig+25-21
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const mem = std.mem;3const mem = std.mem;
3const Allocator = mem.Allocator;4const Allocator = mem.Allocator;
4const process = std.process;5const process = std.process;
...@@ -133,8 +134,9 @@ strip: bool = false,...@@ -133,8 +134,9 @@ strip: bool = false,
133unwindlib: ?[]const u8 = null,134unwindlib: ?[]const u8 = null,
134135
135pub fn deinit(d: *Driver) void {136pub fn deinit(d: *Driver) void {
137 const io = d.comp.io;
136 for (d.link_objects.items[d.link_objects.items.len - d.temp_file_count ..]) |obj| {138 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 {};
138 d.comp.gpa.free(obj);140 d.comp.gpa.free(obj);
139 }141 }
140 d.inputs.deinit(d.comp.gpa);142 d.inputs.deinit(d.comp.gpa);
...@@ -1061,7 +1063,7 @@ pub fn printDiagnosticsStats(d: *Driver) void {...@@ -1061,7 +1063,7 @@ pub fn printDiagnosticsStats(d: *Driver) void {
1061 }1063 }
1062}1064}
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 {
1065 if (d.diagnostics.color == false) return .no_color;1067 if (d.diagnostics.color == false) return .no_color;
1066 const force_color = d.diagnostics.color == true;1068 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_...@@ -1109,7 +1111,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_
1109 defer macro_buf.deinit(d.comp.gpa);1111 defer macro_buf.deinit(d.comp.gpa);
11101112
1111 var stdout_buf: [256]u8 = undefined;1113 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);
1113 if (parseArgs(d, &stdout.interface, &macro_buf, args) catch |er| switch (er) {1115 if (parseArgs(d, &stdout.interface, &macro_buf, args) catch |er| switch (er) {
1114 error.WriteFailed => return d.fatal("failed to write to stdout: {s}", .{errorDescription(er)}),1116 error.WriteFailed => return d.fatal("failed to write to stdout: {s}", .{errorDescription(er)}),
1115 error.OutOfMemory => return error.OutOfMemory,1117 error.OutOfMemory => return error.OutOfMemory,
...@@ -1286,6 +1288,8 @@ fn processSource(...@@ -1286,6 +1288,8 @@ fn processSource(
1286 d.comp.generated_buf.items.len = 0;1288 d.comp.generated_buf.items.len = 0;
1287 const prev_total = d.diagnostics.errors;1289 const prev_total = d.diagnostics.errors;
12881290
1291 const io = d.comp.io;
1292
1289 var pp = try Preprocessor.initDefault(d.comp);1293 var pp = try Preprocessor.initDefault(d.comp);
1290 defer pp.deinit();1294 defer pp.deinit();
12911295
...@@ -1324,13 +1328,13 @@ fn processSource(...@@ -1324,13 +1328,13 @@ fn processSource(
1324 const dep_file_name = try d.getDepFileName(source, writer_buf[0..std.fs.max_name_bytes]);1328 const dep_file_name = try d.getDepFileName(source, writer_buf[0..std.fs.max_name_bytes]);
13251329
1326 const file = if (dep_file_name) |path|1330 const file = if (dep_file_name) |path|
1327 d.comp.cwd.createFile(path, .{}) catch |er|1331 d.comp.cwd.createFile(io, path, .{}) catch |er|
1328 return d.fatal("unable to create dependency file '{s}': {s}", .{ path, errorDescription(er) })1332 return d.fatal("unable to create dependency file '{s}': {s}", .{ path, errorDescription(er) })
1329 else1333 else
1330 std.fs.File.stdout();1334 Io.File.stdout();
1331 defer if (dep_file_name != null) file.close();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);
1334 dep_file.write(&file_writer.interface) catch1338 dep_file.write(&file_writer.interface) catch
1335 return d.fatal("unable to write dependency file: {s}", .{errorDescription(file_writer.err.?)});1339 return d.fatal("unable to write dependency file: {s}", .{errorDescription(file_writer.err.?)});
1336 }1340 }
...@@ -1349,13 +1353,13 @@ fn processSource(...@@ -1349,13 +1353,13 @@ fn processSource(
1349 }1353 }
13501354
1351 const file = if (d.output_name) |some|1355 const file = if (d.output_name) |some|
1352 d.comp.cwd.createFile(some, .{}) catch |er|1356 d.comp.cwd.createFile(io, some, .{}) catch |er|
1353 return d.fatal("unable to create output file '{s}': {s}", .{ some, errorDescription(er) })1357 return d.fatal("unable to create output file '{s}': {s}", .{ some, errorDescription(er) })
1354 else1358 else
1355 std.fs.File.stdout();1359 Io.File.stdout();
1356 defer if (d.output_name != null) file.close();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);
1359 pp.prettyPrintTokens(&file_writer.interface, dump_mode) catch1363 pp.prettyPrintTokens(&file_writer.interface, dump_mode) catch
1360 return d.fatal("unable to write result: {s}", .{errorDescription(file_writer.err.?)});1364 return d.fatal("unable to write result: {s}", .{errorDescription(file_writer.err.?)});
13611365
...@@ -1367,7 +1371,7 @@ fn processSource(...@@ -1367,7 +1371,7 @@ fn processSource(
1367 defer tree.deinit();1371 defer tree.deinit();
13681372
1369 if (d.verbose_ast) {1373 if (d.verbose_ast) {
1370 var stdout = std.fs.File.stdout().writer(&writer_buf);1374 var stdout = Io.File.stdout().writer(&writer_buf);
1371 tree.dump(d.detectConfig(stdout.file), &stdout.interface) catch {};1375 tree.dump(d.detectConfig(stdout.file), &stdout.interface) catch {};
1372 }1376 }
13731377
...@@ -1402,9 +1406,9 @@ fn processSource(...@@ -1402,9 +1406,9 @@ fn processSource(
1402 defer assembly.deinit(gpa);1406 defer assembly.deinit(gpa);
14031407
1404 if (d.only_preprocess_and_compile) {1408 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|
1406 return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) });1410 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
1409 assembly.writeToFile(out_file) catch |er|1413 assembly.writeToFile(out_file) catch |er|
1410 return d.fatal("unable to write to output file '{s}': {s}", .{ out_file_name, errorDescription(er) });1414 return d.fatal("unable to write to output file '{s}': {s}", .{ out_file_name, errorDescription(er) });
...@@ -1416,9 +1420,9 @@ fn processSource(...@@ -1416,9 +1420,9 @@ fn processSource(
1416 // then assemble to out_file_name1420 // then assemble to out_file_name
1417 var assembly_name_buf: [std.fs.max_name_bytes]u8 = undefined;1421 var assembly_name_buf: [std.fs.max_name_bytes]u8 = undefined;
1418 const assembly_out_file_name = try d.getRandomFilename(&assembly_name_buf, ".s");1422 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|
1420 return d.fatal("unable to create output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) });1424 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);
1422 assembly.writeToFile(out_file) catch |er|1426 assembly.writeToFile(out_file) catch |er|
1423 return d.fatal("unable to write to output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) });1427 return d.fatal("unable to write to output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) });
1424 try d.invokeAssembler(tc, assembly_out_file_name, out_file_name);1428 try d.invokeAssembler(tc, assembly_out_file_name, out_file_name);
...@@ -1431,7 +1435,7 @@ fn processSource(...@@ -1431,7 +1435,7 @@ fn processSource(
1431 defer ir.deinit(gpa);1435 defer ir.deinit(gpa);
14321436
1433 if (d.verbose_ir) {1437 if (d.verbose_ir) {
1434 var stdout = std.fs.File.stdout().writer(&writer_buf);1438 var stdout = Io.File.stdout().writer(&writer_buf);
1435 ir.dump(gpa, d.detectConfig(stdout.file), &stdout.interface) catch {};1439 ir.dump(gpa, d.detectConfig(stdout.file), &stdout.interface) catch {};
1436 }1440 }
14371441
...@@ -1452,11 +1456,11 @@ fn processSource(...@@ -1452,11 +1456,11 @@ fn processSource(
1452 };1456 };
1453 defer obj.deinit();1457 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|
1456 return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) });1460 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);
1460 obj.finish(&file_writer.interface) catch1464 obj.finish(&file_writer.interface) catch
1461 return d.fatal("could not output to object file '{s}': {s}", .{ out_file_name, errorDescription(file_writer.err.?) });1465 return d.fatal("could not output to object file '{s}': {s}", .{ out_file_name, errorDescription(file_writer.err.?) });
1462 }1466 }
...@@ -1497,7 +1501,7 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compil...@@ -1497,7 +1501,7 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compil
14971501
1498 if (d.verbose_linker_args) {1502 if (d.verbose_linker_args) {
1499 var stdout_buf: [4096]u8 = undefined;1503 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);
1501 dumpLinkerArgs(&stdout.interface, argv.items) catch {1505 dumpLinkerArgs(&stdout.interface, argv.items) catch {
1502 return d.fatal("unable to dump linker args: {s}", .{errorDescription(stdout.err.?)});1506 return d.fatal("unable to dump linker args: {s}", .{errorDescription(stdout.err.?)});
1503 };1507 };
lib/compiler/aro/aro/Driver/Filesystem.zig+21-19
...@@ -1,8 +1,10 @@...@@ -1,8 +1,10 @@
1const std = @import("std");
2const mem = std.mem;
3const builtin = @import("builtin");1const builtin = @import("builtin");
4const is_windows = builtin.os.tag == .windows;2const is_windows = builtin.os.tag == .windows;
53
4const std = @import("std");
5const Io = std.Io;
6const mem = std.std.mem;
7
6fn readFileFake(entries: []const Filesystem.Entry, path: []const u8, buf: []u8) ?[]const u8 {8fn readFileFake(entries: []const Filesystem.Entry, path: []const u8, buf: []u8) ?[]const u8 {
7 @branchHint(.cold);9 @branchHint(.cold);
8 for (entries) |entry| {10 for (entries) |entry| {
...@@ -55,8 +57,8 @@ fn existsFake(entries: []const Filesystem.Entry, path: []const u8) bool {...@@ -55,8 +57,8 @@ fn existsFake(entries: []const Filesystem.Entry, path: []const u8) bool {
55 return false;57 return false;
56}58}
5759
58fn canExecutePosix(path: []const u8) bool {60fn canExecutePosix(io: Io, path: []const u8) bool {
59 std.posix.access(path, std.posix.X_OK) catch return false;61 Io.Dir.accessAbsolute(io, path, .{ .execute = true }) catch return false;
60 // Todo: ensure path is not a directory62 // Todo: ensure path is not a directory
61 return true;63 return true;
62}64}
...@@ -96,7 +98,7 @@ fn findProgramByNamePosix(name: []const u8, path: ?[]const u8, buf: []u8) ?[]con...@@ -96,7 +98,7 @@ fn findProgramByNamePosix(name: []const u8, path: ?[]const u8, buf: []u8) ?[]con
96}98}
9799
98pub const Filesystem = union(enum) {100pub const Filesystem = union(enum) {
99 real: std.fs.Dir,101 real: std.Io.Dir,
100 fake: []const Entry,102 fake: []const Entry,
101103
102 const Entry = struct {104 const Entry = struct {
...@@ -121,7 +123,7 @@ pub const Filesystem = union(enum) {...@@ -121,7 +123,7 @@ pub const Filesystem = union(enum) {
121 base: []const u8,123 base: []const u8,
122 i: usize = 0,124 i: usize = 0,
123125
124 fn next(self: *@This()) !?std.fs.Dir.Entry {126 fn next(self: *@This()) !?std.Io.Dir.Entry {
125 while (self.i < self.entries.len) {127 while (self.i < self.entries.len) {
126 const entry = self.entries[self.i];128 const entry = self.entries[self.i];
127 self.i += 1;129 self.i += 1;
...@@ -130,7 +132,7 @@ pub const Filesystem = union(enum) {...@@ -130,7 +132,7 @@ pub const Filesystem = union(enum) {
130 const remaining = entry.path[self.base.len + 1 ..];132 const remaining = entry.path[self.base.len + 1 ..];
131 if (std.mem.indexOfScalar(u8, remaining, std.fs.path.sep) != null) continue;133 if (std.mem.indexOfScalar(u8, remaining, std.fs.path.sep) != null) continue;
132 const extension = std.fs.path.extension(remaining);134 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;
134 return .{ .name = remaining, .kind = kind };136 return .{ .name = remaining, .kind = kind };
135 }137 }
136 }138 }
...@@ -140,7 +142,7 @@ pub const Filesystem = union(enum) {...@@ -140,7 +142,7 @@ pub const Filesystem = union(enum) {
140 };142 };
141143
142 const Dir = union(enum) {144 const Dir = union(enum) {
143 dir: std.fs.Dir,145 dir: std.Io.Dir,
144 fake: FakeDir,146 fake: FakeDir,
145147
146 pub fn iterate(self: Dir) Iterator {148 pub fn iterate(self: Dir) Iterator {
...@@ -150,19 +152,19 @@ pub const Filesystem = union(enum) {...@@ -150,19 +152,19 @@ pub const Filesystem = union(enum) {
150 };152 };
151 }153 }
152154
153 pub fn close(self: *Dir) void {155 pub fn close(self: *Dir, io: Io) void {
154 switch (self.*) {156 switch (self.*) {
155 .dir => |*d| d.close(),157 .dir => |*d| d.close(io),
156 .fake => {},158 .fake => {},
157 }159 }
158 }160 }
159 };161 };
160162
161 const Iterator = union(enum) {163 const Iterator = union(enum) {
162 iterator: std.fs.Dir.Iterator,164 iterator: std.Io.Dir.Iterator,
163 fake: FakeDir.Iterator,165 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 {
166 return switch (self.*) {168 return switch (self.*) {
167 .iterator => |*it| it.next(),169 .iterator => |*it| it.next(),
168 .fake => |*it| it.next(),170 .fake => |*it| it.next(),
...@@ -170,10 +172,10 @@ pub const Filesystem = union(enum) {...@@ -170,10 +172,10 @@ pub const Filesystem = union(enum) {
170 }172 }
171 };173 };
172174
173 pub fn exists(fs: Filesystem, path: []const u8) bool {175 pub fn exists(fs: Filesystem, io: Io, path: []const u8) bool {
174 switch (fs) {176 switch (fs) {
175 .real => |cwd| {177 .real => |cwd| {
176 cwd.access(path, .{}) catch return false;178 cwd.access(io, path, .{}) catch return false;
177 return true;179 return true;
178 },180 },
179 .fake => |paths| return existsFake(paths, path),181 .fake => |paths| return existsFake(paths, path),
...@@ -208,11 +210,11 @@ pub const Filesystem = union(enum) {...@@ -208,11 +210,11 @@ pub const Filesystem = union(enum) {
208 /// Read the file at `path` into `buf`.210 /// Read the file at `path` into `buf`.
209 /// Returns null if any errors are encountered211 /// Returns null if any errors are encountered
210 /// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned212 /// 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 {
212 return switch (fs) {214 return switch (fs) {
213 .real => |cwd| {215 .real => |cwd| {
214 const file = cwd.openFile(path, .{}) catch return null;216 const file = cwd.openFile(io, path, .{}) catch return null;
215 defer file.close();217 defer file.close(io);
216218
217 const bytes_read = file.readAll(buf) catch return null;219 const bytes_read = file.readAll(buf) catch return null;
218 return buf[0..bytes_read];220 return buf[0..bytes_read];
...@@ -221,9 +223,9 @@ pub const Filesystem = union(enum) {...@@ -221,9 +223,9 @@ pub const Filesystem = union(enum) {
221 };223 };
222 }224 }
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 {
225 return switch (fs) {227 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 }) },
227 .fake => |entries| .{ .fake = .{ .entries = entries, .path = dir_name } },229 .fake => |entries| .{ .fake = .{ .entries = entries, .path = dir_name } },
228 };230 };
229 }231 }
lib/compiler/aro/aro/Parser.zig+14-13
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const mem = std.mem;3const mem = std.mem;
3const Allocator = mem.Allocator;4const Allocator = mem.Allocator;
4const assert = std.debug.assert;5const assert = std.debug.assert;
...@@ -211,7 +212,7 @@ fn checkIdentifierCodepointWarnings(p: *Parser, codepoint: u21, loc: Source.Loca...@@ -211,7 +212,7 @@ fn checkIdentifierCodepointWarnings(p: *Parser, codepoint: u21, loc: Source.Loca
211212
212 const prev_total = p.diagnostics.total;213 const prev_total = p.diagnostics.total;
213 var sf = std.heap.stackFallback(1024, p.comp.gpa);214 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());
215 defer allocating.deinit();216 defer allocating.deinit();
216217
217 if (!char_info.isC99IdChar(codepoint)) {218 if (!char_info.isC99IdChar(codepoint)) {
...@@ -425,7 +426,7 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype)...@@ -425,7 +426,7 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype)
425 if (p.diagnostics.effectiveKind(diagnostic) == .off) return;426 if (p.diagnostics.effectiveKind(diagnostic) == .off) return;
426427
427 var sf = std.heap.stackFallback(1024, p.comp.gpa);428 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());
429 defer allocating.deinit();430 defer allocating.deinit();
430431
431 p.formatArgs(&allocating.writer, diagnostic.fmt, args) catch return error.OutOfMemory;432 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)...@@ -447,7 +448,7 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype)
447 }, p.pp.expansionSlice(tok_i), true);448 }, p.pp.expansionSlice(tok_i), true);
448}449}
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 {
451 var i: usize = 0;452 var i: usize = 0;
452 inline for (std.meta.fields(@TypeOf(args))) |arg_info| {453 inline for (std.meta.fields(@TypeOf(args))) |arg_info| {
453 const arg = @field(args, arg_info.name);454 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...@@ -476,13 +477,13 @@ fn formatArgs(p: *Parser, w: *std.Io.Writer, fmt: []const u8, args: anytype) !vo
476 try w.writeAll(fmt[i..]);477 try w.writeAll(fmt[i..]);
477}478}
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 {
480 const i = Diagnostics.templateIndex(w, fmt, "{tok_id}");481 const i = Diagnostics.templateIndex(w, fmt, "{tok_id}");
481 try w.writeAll(tok_id.symbol());482 try w.writeAll(tok_id.symbol());
482 return i;483 return i;
483}484}
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 {
486 const i = Diagnostics.templateIndex(w, fmt, "{qt}");487 const i = Diagnostics.templateIndex(w, fmt, "{qt}");
487 try w.writeByte('\'');488 try w.writeByte('\'');
488 try qt.print(p.comp, w);489 try qt.print(p.comp, w);
...@@ -501,7 +502,7 @@ fn formatQualType(p: *Parser, w: *std.Io.Writer, fmt: []const u8, qt: QualType)...@@ -501,7 +502,7 @@ fn formatQualType(p: *Parser, w: *std.Io.Writer, fmt: []const u8, qt: QualType)
501 return i;502 return i;
502}503}
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 {
505 const i = Diagnostics.templateIndex(w, fmt, "{value}");506 const i = Diagnostics.templateIndex(w, fmt, "{value}");
506 switch (res.val.opt_ref) {507 switch (res.val.opt_ref) {
507 .none => try w.writeAll("(none)"),508 .none => try w.writeAll("(none)"),
...@@ -524,7 +525,7 @@ const Normalized = struct {...@@ -524,7 +525,7 @@ const Normalized = struct {
524 return .{ .str = str };525 return .{ .str = str };
525 }526 }
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 {
528 const i = Diagnostics.templateIndex(w, fmt, "{normalized}");529 const i = Diagnostics.templateIndex(w, fmt, "{normalized}");
529 var it: std.unicode.Utf8Iterator = .{530 var it: std.unicode.Utf8Iterator = .{
530 .bytes = ctx.str,531 .bytes = ctx.str,
...@@ -558,7 +559,7 @@ const Codepoint = struct {...@@ -558,7 +559,7 @@ const Codepoint = struct {
558 return .{ .codepoint = codepoint };559 return .{ .codepoint = codepoint };
559 }560 }
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 {
562 const i = Diagnostics.templateIndex(w, fmt, "{codepoint}");563 const i = Diagnostics.templateIndex(w, fmt, "{codepoint}");
563 try w.print("{X:0>4}", .{ctx.codepoint});564 try w.print("{X:0>4}", .{ctx.codepoint});
564 return i;565 return i;
...@@ -572,7 +573,7 @@ const Escaped = struct {...@@ -572,7 +573,7 @@ const Escaped = struct {
572 return .{ .str = str };573 return .{ .str = str };
573 }574 }
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 {
576 const i = Diagnostics.templateIndex(w, fmt, "{s}");577 const i = Diagnostics.templateIndex(w, fmt, "{s}");
577 try std.zig.stringEscape(ctx.str, w);578 try std.zig.stringEscape(ctx.str, w);
578 return i;579 return i;
...@@ -1453,7 +1454,7 @@ fn decl(p: *Parser) Error!bool {...@@ -1453,7 +1454,7 @@ fn decl(p: *Parser) Error!bool {
1453 return true;1454 return true;
1454}1455}
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 {
1457 const w = &allocating.writer;1458 const w = &allocating.writer;
14581459
1459 const cond = cond_node.get(&p.tree);1460 const cond = cond_node.get(&p.tree);
...@@ -1526,7 +1527,7 @@ fn staticAssert(p: *Parser) Error!bool {...@@ -1526,7 +1527,7 @@ fn staticAssert(p: *Parser) Error!bool {
1526 } else {1527 } else {
1527 if (!res.val.toBool(p.comp)) {1528 if (!res.val.toBool(p.comp)) {
1528 var sf = std.heap.stackFallback(1024, gpa);1529 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());
1530 defer allocating.deinit();1531 defer allocating.deinit();
15311532
1532 if (p.staticAssertMessage(res_node, str, &allocating) catch return error.OutOfMemory) |message| {1533 if (p.staticAssertMessage(res_node, str, &allocating) catch return error.OutOfMemory) |message| {
...@@ -9719,7 +9720,7 @@ fn primaryExpr(p: *Parser) Error!?Result {...@@ -9719,7 +9720,7 @@ fn primaryExpr(p: *Parser) Error!?Result {
9719 qt = some.qt;9720 qt = some.qt;
9720 } else if (p.func.qt) |func_qt| {9721 } else if (p.func.qt) |func_qt| {
9721 var sf = std.heap.stackFallback(1024, gpa);9722 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());
9723 defer allocating.deinit();9724 defer allocating.deinit();
97249725
9725 func_qt.printNamed(p.tokSlice(p.func.name), p.comp, &allocating.writer) catch return error.OutOfMemory;9726 func_qt.printNamed(p.tokSlice(p.func.name), p.comp, &allocating.writer) catch return error.OutOfMemory;
...@@ -10608,7 +10609,7 @@ test "Node locations" {...@@ -10608,7 +10609,7 @@ test "Node locations" {
10608 const arena = arena_state.allocator();10609 const arena = arena_state.allocator();
1060910610
10610 var diagnostics: Diagnostics = .{ .output = .ignore };10611 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());
10612 defer comp.deinit();10613 defer comp.deinit();
1061310614
10614 const file = try comp.addSourceFromBuffer("file.c",10615 const file = try comp.addSourceFromBuffer("file.c",
lib/compiler/aro/aro/Preprocessor.zig+8-5
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const mem = std.mem;3const mem = std.mem;
3const Allocator = mem.Allocator;4const Allocator = mem.Allocator;
4const assert = std.debug.assert;5const assert = std.debug.assert;
...@@ -1064,11 +1065,13 @@ fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []con...@@ -1064,11 +1065,13 @@ fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []con
10641065
1065fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) void {1066fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) void {
1066 @branchHint(.cold);1067 @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);
1068 const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start });1071 const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start });
10691072
1070 var stderr_buf: [4096]u8 = undefined;1073 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);
1072 const w = &stderr.interface;1075 const w = &stderr.interface;
10731076
1074 w.print("{s}:{d}:{d}: ", .{ source.path, line_col.line_no, line_col.col }) catch return;1077 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" {...@@ -3899,7 +3902,7 @@ test "Preserve pragma tokens sometimes" {
3899 defer arena.deinit();3902 defer arena.deinit();
39003903
3901 var diagnostics: Diagnostics = .{ .output = .ignore };3904 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());
3903 defer comp.deinit();3906 defer comp.deinit();
39043907
3905 try comp.addDefaultPragmaHandlers();3908 try comp.addDefaultPragmaHandlers();
...@@ -3966,7 +3969,7 @@ test "destringify" {...@@ -3966,7 +3969,7 @@ test "destringify" {
3966 var arena: std.heap.ArenaAllocator = .init(gpa);3969 var arena: std.heap.ArenaAllocator = .init(gpa);
3967 defer arena.deinit();3970 defer arena.deinit();
3968 var diagnostics: Diagnostics = .{ .output = .ignore };3971 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());
3970 defer comp.deinit();3973 defer comp.deinit();
3971 var pp = Preprocessor.init(&comp, .default);3974 var pp = Preprocessor.init(&comp, .default);
3972 defer pp.deinit();3975 defer pp.deinit();
...@@ -4029,7 +4032,7 @@ test "Include guards" {...@@ -4029,7 +4032,7 @@ test "Include guards" {
4029 const arena = arena_state.allocator();4032 const arena = arena_state.allocator();
40304033
4031 var diagnostics: Diagnostics = .{ .output = .ignore };4034 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());
4033 defer comp.deinit();4036 defer comp.deinit();
4034 var pp = Preprocessor.init(&comp, .default);4037 var pp = Preprocessor.init(&comp, .default);
4035 defer pp.deinit();4038 defer pp.deinit();
lib/compiler/aro/aro/Tokenizer.zig+3-2
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const assert = std.debug.assert;3const assert = std.debug.assert;
34
4const Compilation = @import("Compilation.zig");5const Compilation = @import("Compilation.zig");
...@@ -2326,7 +2327,7 @@ test "Tokenizer fuzz test" {...@@ -2326,7 +2327,7 @@ test "Tokenizer fuzz test" {
2326 fn testOne(_: @This(), input_bytes: []const u8) anyerror!void {2327 fn testOne(_: @This(), input_bytes: []const u8) anyerror!void {
2327 var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);2328 var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
2328 defer arena.deinit();2329 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());
2330 defer comp.deinit();2331 defer comp.deinit();
23312332
2332 const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes);2333 const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes);
...@@ -2351,7 +2352,7 @@ test "Tokenizer fuzz test" {...@@ -2351,7 +2352,7 @@ test "Tokenizer fuzz test" {
2351fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, langopts: ?LangOpts) !void {2352fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, langopts: ?LangOpts) !void {
2352 var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);2353 var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
2353 defer arena.deinit();2354 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());
2355 defer comp.deinit();2356 defer comp.deinit();
2356 if (langopts) |provided| {2357 if (langopts) |provided| {
2357 comp.langopts = provided;2358 comp.langopts = provided;
lib/compiler/aro/aro/Toolchain.zig+11-7
...@@ -497,10 +497,11 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void {...@@ -497,10 +497,11 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void {
497 const comp = d.comp;497 const comp = d.comp;
498 const gpa = comp.gpa;498 const gpa = comp.gpa;
499 const arena = comp.arena;499 const arena = comp.arena;
500 const io = comp.io;
500 try d.includes.ensureUnusedCapacity(gpa, 1);501 try d.includes.ensureUnusedCapacity(gpa, 1);
501 if (d.resource_dir) |resource_dir| {502 if (d.resource_dir) |resource_dir| {
502 const path = try std.fs.path.join(arena, &.{ resource_dir, "include" });503 const path = try std.fs.path.join(arena, &.{ resource_dir, "include" });
503 comp.cwd.access(path, .{}) catch {504 comp.cwd.access(io, path, .{}) catch {
504 return d.fatal("Aro builtin headers not found in provided -resource-dir", .{});505 return d.fatal("Aro builtin headers not found in provided -resource-dir", .{});
505 };506 };
506 d.includes.appendAssumeCapacity(.{ .kind = .system, .path = path });507 d.includes.appendAssumeCapacity(.{ .kind = .system, .path = path });
...@@ -508,10 +509,10 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void {...@@ -508,10 +509,10 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void {
508 }509 }
509 var search_path = d.aro_name;510 var search_path = d.aro_name;
510 while (std.fs.path.dirname(search_path)) |dirname| : (search_path = dirname) {511 while (std.fs.path.dirname(search_path)) |dirname| : (search_path = dirname) {
511 var base_dir = d.comp.cwd.openDir(dirname, .{}) catch continue;512 var base_dir = d.comp.cwd.openDir(io, dirname, .{}) catch continue;
512 defer base_dir.close();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;
515 const path = try std.fs.path.join(arena, &.{ dirname, "include" });516 const path = try std.fs.path.join(arena, &.{ dirname, "include" });
516 d.includes.appendAssumeCapacity(.{ .kind = .system, .path = path });517 d.includes.appendAssumeCapacity(.{ .kind = .system, .path = path });
517 break;518 break;
...@@ -523,12 +524,14 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void {...@@ -523,12 +524,14 @@ pub fn addBuiltinIncludeDir(tc: *const Toolchain) !void {
523/// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned524/// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned
524pub fn readFile(tc: *const Toolchain, path: []const u8, buf: []u8) ?[]const u8 {525pub fn readFile(tc: *const Toolchain, path: []const u8, buf: []u8) ?[]const u8 {
525 const comp = tc.driver.comp;526 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;
527}529}
528530
529pub fn exists(tc: *const Toolchain, path: []const u8) bool {531pub fn exists(tc: *const Toolchain, path: []const u8) bool {
530 const comp = tc.driver.comp;532 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;
532 return true;535 return true;
533}536}
534537
...@@ -546,7 +549,8 @@ pub fn canExecute(tc: *const Toolchain, path: []const u8) bool {...@@ -546,7 +549,8 @@ pub fn canExecute(tc: *const Toolchain, path: []const u8) bool {
546 }549 }
547550
548 const comp = tc.driver.comp;551 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;
550 // Todo: ensure path is not a directory554 // Todo: ensure path is not a directory
551 return true;555 return true;
552}556}
lib/compiler/aro/aro/Value.zig+6-5
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const assert = std.debug.assert;3const assert = std.debug.assert;
3const BigIntConst = std.math.big.int.Const;4const BigIntConst = std.math.big.int.Const;
4const BigIntMutable = std.math.big.int.Mutable;5const BigIntMutable = std.math.big.int.Mutable;
...@@ -80,7 +81,7 @@ test "minUnsignedBits" {...@@ -80,7 +81,7 @@ test "minUnsignedBits" {
80 defer arena_state.deinit();81 defer arena_state.deinit();
81 const arena = arena_state.allocator();82 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());
84 defer comp.deinit();85 defer comp.deinit();
85 const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" });86 const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" });
86 comp.target = .fromZigTarget(try std.zig.system.resolveTargetQuery(std.testing.io, target_query));87 comp.target = .fromZigTarget(try std.zig.system.resolveTargetQuery(std.testing.io, target_query));
...@@ -119,7 +120,7 @@ test "minSignedBits" {...@@ -119,7 +120,7 @@ test "minSignedBits" {
119 defer arena_state.deinit();120 defer arena_state.deinit();
120 const arena = arena_state.allocator();121 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());
123 defer comp.deinit();124 defer comp.deinit();
124 const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" });125 const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" });
125 comp.target = .fromZigTarget(try std.zig.system.resolveTargetQuery(std.testing.io, target_query));126 comp.target = .fromZigTarget(try std.zig.system.resolveTargetQuery(std.testing.io, target_query));
...@@ -1080,7 +1081,7 @@ const NestedPrint = union(enum) {...@@ -1080,7 +1081,7 @@ const NestedPrint = union(enum) {
1080 },1081 },
1081};1082};
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 {
1084 try w.writeByte('&');1085 try w.writeByte('&');
1085 try w.writeAll(base);1086 try w.writeAll(base);
1086 if (!offset.isZero(comp)) {1087 if (!offset.isZero(comp)) {
...@@ -1089,7 +1090,7 @@ pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w...@@ -1089,7 +1090,7 @@ pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w
1089 }1090 }
1090}1091}
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 {
1093 if (qt.is(comp, .bool)) {1094 if (qt.is(comp, .bool)) {
1094 try w.writeAll(if (v.isZero(comp)) "false" else "true");1095 try w.writeAll(if (v.isZero(comp)) "false" else "true");
1095 return null;1096 return null;
...@@ -1116,7 +1117,7 @@ pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *std.Io.Writer...@@ -1116,7 +1117,7 @@ pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *std.Io.Writer
1116 return null;1117 return null;
1117}1118}
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 {
1120 const size: Compilation.CharUnitSize = @enumFromInt(qt.childType(comp).sizeof(comp));1121 const size: Compilation.CharUnitSize = @enumFromInt(qt.childType(comp).sizeof(comp));
1121 const without_null = bytes[0 .. bytes.len - @intFromEnum(size)];1122 const without_null = bytes[0 .. bytes.len - @intFromEnum(size)];
1122 try w.writeByte('"');1123 try w.writeByte('"');
lib/compiler/aro/backend/Assembly.zig+3-2
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
34
4data: []const u8,5data: []const u8,
...@@ -11,8 +12,8 @@ pub fn deinit(self: *const Assembly, gpa: Allocator) void {...@@ -11,8 +12,8 @@ pub fn deinit(self: *const Assembly, gpa: Allocator) void {
11 gpa.free(self.text);12 gpa.free(self.text);
12}13}
1314
14pub fn writeToFile(self: Assembly, file: std.fs.File) !void {15pub fn writeToFile(self: Assembly, io: Io, file: Io.File) !void {
15 var file_writer = file.writer(&.{});16 var file_writer = file.writer(io, &.{});
1617
17 var buffers = [_][]const u8{ self.data, self.text };18 var buffers = [_][]const u8{ self.data, self.text };
18 try file_writer.interface.writeSplatAll(&buffers, 1);19 try file_writer.interface.writeSplatAll(&buffers, 1);
lib/compiler/aro/main.zig+5-4
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const Allocator = mem.Allocator;3const Allocator = mem.Allocator;
3const mem = std.mem;4const mem = std.mem;
4const process = std.process;5const process = std.process;
...@@ -30,7 +31,7 @@ pub fn main() u8 {...@@ -30,7 +31,7 @@ pub fn main() u8 {
30 defer arena_instance.deinit();31 defer arena_instance.deinit();
31 const arena = arena_instance.allocator();32 const arena = arena_instance.allocator();
3233
33 var threaded: std.Io.Threaded = .init(gpa);34 var threaded: std.Io.Threaded = .init(gpa, .{});
34 defer threaded.deinit();35 defer threaded.deinit();
35 const io = threaded.io();36 const io = threaded.io();
3637
...@@ -42,7 +43,7 @@ pub fn main() u8 {...@@ -42,7 +43,7 @@ pub fn main() u8 {
42 return 1;43 return 1;
43 };44 };
4445
45 const aro_name = std.fs.selfExePathAlloc(gpa) catch {46 const aro_name = process.executablePathAlloc(io, gpa) catch {
46 std.debug.print("unable to find Aro executable path\n", .{});47 std.debug.print("unable to find Aro executable path\n", .{});
47 if (fast_exit) process.exit(1);48 if (fast_exit) process.exit(1);
48 return 1;49 return 1;
...@@ -50,7 +51,7 @@ pub fn main() u8 {...@@ -50,7 +51,7 @@ pub fn main() u8 {
50 defer gpa.free(aro_name);51 defer gpa.free(aro_name);
5152
52 var stderr_buf: [1024]u8 = undefined;53 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);
54 var diagnostics: Diagnostics = .{55 var diagnostics: Diagnostics = .{
55 .output = .{ .to_writer = .{56 .output = .{ .to_writer = .{
56 .color = .detect(stderr.file),57 .color = .detect(stderr.file),
...@@ -58,7 +59,7 @@ pub fn main() u8 {...@@ -58,7 +59,7 @@ pub fn main() u8 {
58 } },59 } },
59 };60 };
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) {
62 error.OutOfMemory => {63 error.OutOfMemory => {
63 std.debug.print("out of memory\n", .{});64 std.debug.print("out of memory\n", .{});
64 if (fast_exit) process.exit(1);65 if (fast_exit) process.exit(1);
lib/compiler/build_runner.zig+195-198
...@@ -7,14 +7,13 @@ const assert = std.debug.assert;...@@ -7,14 +7,13 @@ const assert = std.debug.assert;
7const fmt = std.fmt;7const fmt = std.fmt;
8const mem = std.mem;8const mem = std.mem;
9const process = std.process;9const process = std.process;
10const File = std.fs.File;10const File = std.Io.File;
11const Step = std.Build.Step;11const Step = std.Build.Step;
12const Watch = std.Build.Watch;12const Watch = std.Build.Watch;
13const WebServer = std.Build.WebServer;13const WebServer = std.Build.WebServer;
14const Allocator = std.mem.Allocator;14const Allocator = std.mem.Allocator;
15const fatal = std.process.fatal;15const fatal = std.process.fatal;
16const Writer = std.Io.Writer;16const Writer = std.Io.Writer;
17const tty = std.Io.tty;
1817
19pub const root = @import("@build");18pub const root = @import("@build");
20pub const dependencies = @import("@dependencies");19pub const dependencies = @import("@dependencies");
...@@ -40,7 +39,7 @@ pub fn main() !void {...@@ -40,7 +39,7 @@ pub fn main() !void {
4039
41 const args = try process.argsAlloc(arena);40 const args = try process.argsAlloc(arena);
4241
43 var threaded: std.Io.Threaded = .init(gpa);42 var threaded: std.Io.Threaded = .init(gpa, .{});
44 defer threaded.deinit();43 defer threaded.deinit();
45 const io = threaded.io();44 const io = threaded.io();
4645
...@@ -53,24 +52,26 @@ pub fn main() !void {...@@ -53,24 +52,26 @@ pub fn main() !void {
53 const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{});52 const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{});
54 const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{});53 const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{});
5554
55 const cwd: Io.Dir = .cwd();
56
56 const zig_lib_directory: std.Build.Cache.Directory = .{57 const zig_lib_directory: std.Build.Cache.Directory = .{
57 .path = zig_lib_dir,58 .path = zig_lib_dir,
58 .handle = try std.fs.cwd().openDir(zig_lib_dir, .{}),59 .handle = try cwd.openDir(io, zig_lib_dir, .{}),
59 };60 };
6061
61 const build_root_directory: std.Build.Cache.Directory = .{62 const build_root_directory: std.Build.Cache.Directory = .{
62 .path = build_root,63 .path = build_root,
63 .handle = try std.fs.cwd().openDir(build_root, .{}),64 .handle = try cwd.openDir(io, build_root, .{}),
64 };65 };
6566
66 const local_cache_directory: std.Build.Cache.Directory = .{67 const local_cache_directory: std.Build.Cache.Directory = .{
67 .path = cache_root,68 .path = cache_root,
68 .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}),69 .handle = try cwd.createDirPathOpen(io, cache_root, .{}),
69 };70 };
7071
71 const global_cache_directory: std.Build.Cache.Directory = .{72 const global_cache_directory: std.Build.Cache.Directory = .{
72 .path = global_cache_root,73 .path = global_cache_root,
73 .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),74 .handle = try cwd.createDirPathOpen(io, global_cache_root, .{}),
74 };75 };
7576
76 var graph: std.Build.Graph = .{77 var graph: std.Build.Graph = .{
...@@ -79,7 +80,7 @@ pub fn main() !void {...@@ -79,7 +80,7 @@ pub fn main() !void {
79 .cache = .{80 .cache = .{
80 .io = io,81 .io = io,
81 .gpa = arena,82 .gpa = arena,
82 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),83 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),
83 },84 },
84 .zig_exe = zig_exe,85 .zig_exe = zig_exe,
85 .env_map = try process.getEnvMap(arena),86 .env_map = try process.getEnvMap(arena),
...@@ -92,7 +93,7 @@ pub fn main() !void {...@@ -92,7 +93,7 @@ pub fn main() !void {
92 .time_report = false,93 .time_report = false,
93 };94 };
9495
95 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });96 graph.cache.addPrefix(.{ .path = null, .handle = cwd });
96 graph.cache.addPrefix(build_root_directory);97 graph.cache.addPrefix(build_root_directory);
97 graph.cache.addPrefix(local_cache_directory);98 graph.cache.addPrefix(local_cache_directory);
98 graph.cache.addPrefix(global_cache_directory);99 graph.cache.addPrefix(global_cache_directory);
...@@ -285,8 +286,8 @@ pub fn main() !void {...@@ -285,8 +286,8 @@ pub fn main() !void {
285 const next_arg = nextArg(args, &arg_idx) orelse286 const next_arg = nextArg(args, &arg_idx) orelse
286 fatalWithHint("expected u16 after '{s}'", .{arg});287 fatalWithHint("expected u16 after '{s}'", .{arg});
287 debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| {288 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 fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {t}\n", .{
289 next_arg, @errorName(err),290 next_arg, err,
290 });291 });
291 };292 };
292 } else if (mem.eql(u8, arg, "--webui")) {293 } else if (mem.eql(u8, arg, "--webui")) {
...@@ -428,14 +429,21 @@ pub fn main() !void {...@@ -428,14 +429,21 @@ pub fn main() !void {
428 }429 }
429 }430 }
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
431 if (webui_listen != null) {441 if (webui_listen != null) {
432 if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{});442 if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{});
433 if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{});443 if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{});
434 }444 }
435445
436 const ttyconf = color.detectTtyConf();446 const main_progress_node = std.Progress.start(io, .{
437
438 const main_progress_node = std.Progress.start(.{
439 .disable_printing = (color == .off),447 .disable_printing = (color == .off),
440 });448 });
441 defer main_progress_node.end();449 defer main_progress_node.end();
...@@ -457,7 +465,7 @@ pub fn main() !void {...@@ -457,7 +465,7 @@ pub fn main() !void {
457 }465 }
458 const s = std.fs.path.sep_str;466 const s = std.fs.path.sep_str;
459 const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{}));467 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, .{
461 .sub_path = tmp_sub_path,469 .sub_path = tmp_sub_path,
462 .data = buffer.items,470 .data = buffer.items,
463 .flags = .{ .exclusive = true },471 .flags = .{ .exclusive = true },
...@@ -476,14 +484,14 @@ pub fn main() !void {...@@ -476,14 +484,14 @@ pub fn main() !void {
476 validateSystemLibraryOptions(builder);484 validateSystemLibraryOptions(builder);
477485
478 if (help_menu) {486 if (help_menu) {
479 var w = initStdoutWriter();487 var w = initStdoutWriter(io);
480 printUsage(builder, w) catch return stdout_writer_allocation.err.?;488 printUsage(builder, w) catch return stdout_writer_allocation.err.?;
481 w.flush() catch return stdout_writer_allocation.err.?;489 w.flush() catch return stdout_writer_allocation.err.?;
482 return;490 return;
483 }491 }
484492
485 if (steps_menu) {493 if (steps_menu) {
486 var w = initStdoutWriter();494 var w = initStdoutWriter(io);
487 printSteps(builder, w) catch return stdout_writer_allocation.err.?;495 printSteps(builder, w) catch return stdout_writer_allocation.err.?;
488 w.flush() catch return stdout_writer_allocation.err.?;496 w.flush() catch return stdout_writer_allocation.err.?;
489 return;497 return;
...@@ -507,8 +515,6 @@ pub fn main() !void {...@@ -507,8 +515,6 @@ pub fn main() !void {
507 .error_style = error_style,515 .error_style = error_style,
508 .multiline_errors = multiline_errors,516 .multiline_errors = multiline_errors,
509 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,517 .summary = summary orelse if (watch or webui_listen != null) .line else .failures,
510
511 .ttyconf = ttyconf,
512 };518 };
513 defer {519 defer {
514 run.memory_blocked_steps.deinit(gpa);520 run.memory_blocked_steps.deinit(gpa);
...@@ -522,10 +528,10 @@ pub fn main() !void {...@@ -522,10 +528,10 @@ pub fn main() !void {
522528
523 prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) {529 prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) {
524 error.DependencyLoopDetected => {530 error.DependencyLoopDetected => {
525 // Perhaps in the future there could be an Advanced Options flag such as531 // Perhaps in the future there could be an Advanced Options flag
526 // --debug-build-runner-leaks which would make this code return instead of532 // such as --debug-build-runner-leaks which would make this code
527 // calling exit.533 // return instead of calling exit.
528 std.debug.lockStdErr();534 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
529 process.exit(1);535 process.exit(1);
530 },536 },
531 else => |e| return e,537 else => |e| return e,
...@@ -543,7 +549,6 @@ pub fn main() !void {...@@ -543,7 +549,6 @@ pub fn main() !void {
543 if (builtin.single_threaded) unreachable; // `fatal` above549 if (builtin.single_threaded) unreachable; // `fatal` above
544 break :ws .init(.{550 break :ws .init(.{
545 .gpa = gpa,551 .gpa = gpa,
546 .ttyconf = ttyconf,
547 .graph = &graph,552 .graph = &graph,
548 .all_steps = run.step_stack.keys(),553 .all_steps = run.step_stack.keys(),
549 .root_prog_node = main_progress_node,554 .root_prog_node = main_progress_node,
...@@ -558,9 +563,9 @@ pub fn main() !void {...@@ -558,9 +563,9 @@ pub fn main() !void {
558 }563 }
559564
560 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {565 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {
561 const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);566 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
562 defer std.debug.unlockStderrWriter();567 defer io.unlockStderr();
563 try bw.writeAll("\x1B[2J\x1B[3J\x1B[H");568 try stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H");
564 }) {569 }) {
565 if (run.web_server) |*ws| ws.startBuild();570 if (run.web_server) |*ws| ws.startBuild();
566571
...@@ -661,9 +666,6 @@ const Run = struct {...@@ -661,9 +666,6 @@ const Run = struct {
661 memory_blocked_steps: std.ArrayList(*Step),666 memory_blocked_steps: std.ArrayList(*Step),
662 /// Allocated into `gpa`.667 /// Allocated into `gpa`.
663 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),668 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
668 claimed_rss: usize,670 claimed_rss: usize,
669 error_style: ErrorStyle,671 error_style: ErrorStyle,
...@@ -737,7 +739,8 @@ fn runStepNames(...@@ -737,7 +739,8 @@ fn runStepNames(
737 fuzz: ?std.Build.Fuzz.Mode,739 fuzz: ?std.Build.Fuzz.Mode,
738) !void {740) !void {
739 const gpa = run.gpa;741 const gpa = run.gpa;
740 const io = b.graph.io;742 const graph = b.graph;
743 const io = graph.io;
741 const step_stack = &run.step_stack;744 const step_stack = &run.step_stack;
742745
743 {746 {
...@@ -822,7 +825,7 @@ fn runStepNames(...@@ -822,7 +825,7 @@ fn runStepNames(
822 }825 }
823 if (@bitSizeOf(usize) != 64) {826 if (@bitSizeOf(usize) != 64) {
824 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,827 // 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 case828 // being compatible with file system's u64 return value. This is not the case
826 // on 32-bit platforms.829 // on 32-bit platforms.
827 // Affects or affected by issues #5185, #22523, and #22464.830 // Affects or affected by issues #5185, #22523, and #22464.
828 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});831 fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
...@@ -837,7 +840,6 @@ fn runStepNames(...@@ -837,7 +840,6 @@ fn runStepNames(
837 var f = std.Build.Fuzz.init(840 var f = std.Build.Fuzz.init(
838 gpa,841 gpa,
839 io,842 io,
840 run.ttyconf,
841 step_stack.keys(),843 step_stack.keys(),
842 parent_prog_node,844 parent_prog_node,
843 mode,845 mode,
...@@ -864,18 +866,19 @@ fn runStepNames(...@@ -864,18 +866,19 @@ fn runStepNames(
864 .none => break :summary,866 .none => break :summary,
865 }867 }
866868
867 const w, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);869 const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode);
868 defer std.debug.unlockStderrWriter();870 defer io.unlockStderr();
869 const ttyconf = run.ttyconf;871 const t = stderr.terminal();
872 const w = &stderr.file_writer.interface;
870873
871 const total_count = success_count + failure_count + pending_count + skipped_count;874 const total_count = success_count + failure_count + pending_count + skipped_count;
872 ttyconf.setColor(w, .cyan) catch {};875 t.setColor(.cyan) catch {};
873 ttyconf.setColor(w, .bold) catch {};876 t.setColor(.bold) catch {};
874 w.writeAll("Build Summary: ") catch {};877 w.writeAll("Build Summary: ") catch {};
875 ttyconf.setColor(w, .reset) catch {};878 t.setColor(.reset) catch {};
876 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};879 w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
877 {880 {
878 ttyconf.setColor(w, .dim) catch {};881 t.setColor(.dim) catch {};
879 var first = true;882 var first = true;
880 if (skipped_count > 0) {883 if (skipped_count > 0) {
881 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};884 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {};
...@@ -886,12 +889,12 @@ fn runStepNames(...@@ -886,12 +889,12 @@ fn runStepNames(
886 first = false;889 first = false;
887 }890 }
888 if (!first) w.writeByte(')') catch {};891 if (!first) w.writeByte(')') catch {};
889 ttyconf.setColor(w, .reset) catch {};892 t.setColor(.reset) catch {};
890 }893 }
891894
892 if (test_count > 0) {895 if (test_count > 0) {
893 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};896 w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
894 ttyconf.setColor(w, .dim) catch {};897 t.setColor(.dim) catch {};
895 var first = true;898 var first = true;
896 if (test_skip_count > 0) {899 if (test_skip_count > 0) {
897 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};900 w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {};
...@@ -910,7 +913,7 @@ fn runStepNames(...@@ -910,7 +913,7 @@ fn runStepNames(
910 first = false;913 first = false;
911 }914 }
912 if (!first) w.writeByte(')') catch {};915 if (!first) w.writeByte(')') catch {};
913 ttyconf.setColor(w, .reset) catch {};916 t.setColor(.reset) catch {};
914 }917 }
915918
916 w.writeAll("\n") catch {};919 w.writeAll("\n") catch {};
...@@ -924,7 +927,7 @@ fn runStepNames(...@@ -924,7 +927,7 @@ fn runStepNames(
924 var print_node: PrintNode = .{ .parent = null };927 var print_node: PrintNode = .{ .parent = null };
925 if (step_names.len == 0) {928 if (step_names.len == 0) {
926 print_node.last = true;929 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 {};
928 } else {931 } else {
929 const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {932 const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: {
930 var i: usize = step_names.len;933 var i: usize = step_names.len;
...@@ -943,7 +946,7 @@ fn runStepNames(...@@ -943,7 +946,7 @@ fn runStepNames(
943 for (step_names, 0..) |step_name, i| {946 for (step_names, 0..) |step_name, i| {
944 const tls = b.top_level_steps.get(step_name).?;947 const tls = b.top_level_steps.get(step_name).?;
945 print_node.last = i + 1 == last_index;948 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 {};
947 }950 }
948 }951 }
949 w.writeByte('\n') catch {};952 w.writeByte('\n') catch {};
...@@ -960,7 +963,7 @@ fn runStepNames(...@@ -960,7 +963,7 @@ fn runStepNames(
960 if (run.error_style.verboseContext()) break :code 1; // failure; print build command963 if (run.error_style.verboseContext()) break :code 1; // failure; print build command
961 break :code 2; // failure; do not print build command964 break :code 2; // failure; do not print build command
962 };965 };
963 std.debug.lockStdErr();966 _ = io.lockStderr(&.{}, graph.stderr_mode) catch {};
964 process.exit(code);967 process.exit(code);
965}968}
966969
...@@ -969,33 +972,30 @@ const PrintNode = struct {...@@ -969,33 +972,30 @@ const PrintNode = struct {
969 last: bool = false,972 last: bool = false,
970};973};
971974
972fn printPrefix(node: *PrintNode, stderr: *Writer, ttyconf: tty.Config) !void {975fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void {
973 const parent = node.parent orelse return;976 const parent = node.parent orelse return;
977 const writer = stderr.writer;
974 if (parent.parent == null) return;978 if (parent.parent == null) return;
975 try printPrefix(parent, stderr, ttyconf);979 try printPrefix(parent, stderr);
976 if (parent.last) {980 if (parent.last) {
977 try stderr.writeAll(" ");981 try writer.writeAll(" ");
978 } else {982 } else {
979 try stderr.writeAll(switch (ttyconf) {983 try writer.writeAll(switch (stderr.mode) {
980 .no_color, .windows_api => "| ",
981 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │984 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
985 else => "| ",
982 });986 });
983 }987 }
984}988}
985989
986fn printChildNodePrefix(stderr: *Writer, ttyconf: tty.Config) !void {990fn printChildNodePrefix(stderr: Io.Terminal) !void {
987 try stderr.writeAll(switch (ttyconf) {991 try stderr.writer.writeAll(switch (stderr.mode) {
988 .no_color, .windows_api => "+- ",
989 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─992 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
993 else => "+- ",
990 });994 });
991}995}
992996
993fn printStepStatus(997fn printStepStatus(s: *Step, stderr: Io.Terminal, run: *const Run) !void {
994 s: *Step,998 const writer = stderr.writer;
995 stderr: *Writer,
996 ttyconf: tty.Config,
997 run: *const Run,
998) !void {
999 switch (s.state) {999 switch (s.state) {
1000 .precheck_unstarted => unreachable,1000 .precheck_unstarted => unreachable,
1001 .precheck_started => unreachable,1001 .precheck_started => unreachable,
...@@ -1003,139 +1003,135 @@ fn printStepStatus(...@@ -1003,139 +1003,135 @@ fn printStepStatus(
1003 .running => unreachable,1003 .running => unreachable,
10041004
1005 .dependency_failure => {1005 .dependency_failure => {
1006 try ttyconf.setColor(stderr, .dim);1006 try stderr.setColor(.dim);
1007 try stderr.writeAll(" transitive failure\n");1007 try writer.writeAll(" transitive failure\n");
1008 try ttyconf.setColor(stderr, .reset);1008 try stderr.setColor(.reset);
1009 },1009 },
10101010
1011 .success => {1011 .success => {
1012 try ttyconf.setColor(stderr, .green);1012 try stderr.setColor(.green);
1013 if (s.result_cached) {1013 if (s.result_cached) {
1014 try stderr.writeAll(" cached");1014 try writer.writeAll(" cached");
1015 } else if (s.test_results.test_count > 0) {1015 } else if (s.test_results.test_count > 0) {
1016 const pass_count = s.test_results.passCount();1016 const pass_count = s.test_results.passCount();
1017 assert(s.test_results.test_count == pass_count + s.test_results.skip_count);1017 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});
1019 if (s.test_results.skip_count > 0) {1019 if (s.test_results.skip_count > 0) {
1020 try ttyconf.setColor(stderr, .reset);1020 try stderr.setColor(.reset);
1021 try stderr.writeAll(", ");1021 try writer.writeAll(", ");
1022 try ttyconf.setColor(stderr, .yellow);1022 try stderr.setColor(.yellow);
1023 try stderr.print("{d} skip", .{s.test_results.skip_count});1023 try writer.print("{d} skip", .{s.test_results.skip_count});
1024 }1024 }
1025 try ttyconf.setColor(stderr, .reset);1025 try stderr.setColor(.reset);
1026 try stderr.print(" ({d} total)", .{s.test_results.test_count});1026 try writer.print(" ({d} total)", .{s.test_results.test_count});
1027 } else {1027 } else {
1028 try stderr.writeAll(" success");1028 try writer.writeAll(" success");
1029 }1029 }
1030 try ttyconf.setColor(stderr, .reset);1030 try stderr.setColor(.reset);
1031 if (s.result_duration_ns) |ns| {1031 if (s.result_duration_ns) |ns| {
1032 try ttyconf.setColor(stderr, .dim);1032 try stderr.setColor(.dim);
1033 if (ns >= std.time.ns_per_min) {1033 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});
1035 } else if (ns >= std.time.ns_per_s) {1035 } 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});
1037 } else if (ns >= std.time.ns_per_ms) {1037 } 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});
1039 } else if (ns >= std.time.ns_per_us) {1039 } 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});
1041 } else {1041 } else {
1042 try stderr.print(" {d}ns", .{ns});1042 try writer.print(" {d}ns", .{ns});
1043 }1043 }
1044 try ttyconf.setColor(stderr, .reset);1044 try stderr.setColor(.reset);
1045 }1045 }
1046 if (s.result_peak_rss != 0) {1046 if (s.result_peak_rss != 0) {
1047 const rss = s.result_peak_rss;1047 const rss = s.result_peak_rss;
1048 try ttyconf.setColor(stderr, .dim);1048 try stderr.setColor(.dim);
1049 if (rss >= 1000_000_000) {1049 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});
1051 } else if (rss >= 1000_000) {1051 } 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});
1053 } else if (rss >= 1000) {1053 } else if (rss >= 1000) {
1054 try stderr.print(" MaxRSS:{d}K", .{rss / 1000});1054 try writer.print(" MaxRSS:{d}K", .{rss / 1000});
1055 } else {1055 } else {
1056 try stderr.print(" MaxRSS:{d}B", .{rss});1056 try writer.print(" MaxRSS:{d}B", .{rss});
1057 }1057 }
1058 try ttyconf.setColor(stderr, .reset);1058 try stderr.setColor(.reset);
1059 }1059 }
1060 try stderr.writeAll("\n");1060 try writer.writeAll("\n");
1061 },1061 },
1062 .skipped, .skipped_oom => |skip| {1062 .skipped, .skipped_oom => |skip| {
1063 try ttyconf.setColor(stderr, .yellow);1063 try stderr.setColor(.yellow);
1064 try stderr.writeAll(" skipped");1064 try writer.writeAll(" skipped");
1065 if (skip == .skipped_oom) {1065 if (skip == .skipped_oom) {
1066 try stderr.writeAll(" (not enough memory)");1066 try writer.writeAll(" (not enough memory)");
1067 try ttyconf.setColor(stderr, .dim);1067 try stderr.setColor(.dim);
1068 try stderr.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });1068 try writer.print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
1069 try ttyconf.setColor(stderr, .yellow);1069 try stderr.setColor(.yellow);
1070 }1070 }
1071 try stderr.writeAll("\n");1071 try writer.writeAll("\n");
1072 try ttyconf.setColor(stderr, .reset);1072 try stderr.setColor(.reset);
1073 },1073 },
1074 .failure => {1074 .failure => {
1075 try printStepFailure(s, stderr, ttyconf, false);1075 try printStepFailure(s, stderr, false);
1076 try ttyconf.setColor(stderr, .reset);1076 try stderr.setColor(.reset);
1077 },1077 },
1078 }1078 }
1079}1079}
10801080
1081fn printStepFailure(1081fn printStepFailure(s: *Step, stderr: Io.Terminal, dim: bool) !void {
1082 s: *Step,1082 const w = stderr.writer;
1083 stderr: *Writer,
1084 ttyconf: tty.Config,
1085 dim: bool,
1086) !void {
1087 if (s.result_error_bundle.errorMessageCount() > 0) {1083 if (s.result_error_bundle.errorMessageCount() > 0) {
1088 try ttyconf.setColor(stderr, .red);1084 try stderr.setColor(.red);
1089 try stderr.print(" {d} errors\n", .{1085 try w.print(" {d} errors\n", .{
1090 s.result_error_bundle.errorMessageCount(),1086 s.result_error_bundle.errorMessageCount(),
1091 });1087 });
1092 } else if (!s.test_results.isSuccess()) {1088 } else if (!s.test_results.isSuccess()) {
1093 // These first values include all of the test "statuses". Every test is either passsed,1089 // These first values include all of the test "statuses". Every test is either passsed,
1094 // skipped, failed, crashed, or timed out.1090 // skipped, failed, crashed, or timed out.
1095 try ttyconf.setColor(stderr, .green);1091 try stderr.setColor(.green);
1096 try stderr.print(" {d} pass", .{s.test_results.passCount()});1092 try w.print(" {d} pass", .{s.test_results.passCount()});
1097 try ttyconf.setColor(stderr, .reset);1093 try stderr.setColor(.reset);
1098 if (dim) try ttyconf.setColor(stderr, .dim);1094 if (dim) try stderr.setColor(.dim);
1099 if (s.test_results.skip_count > 0) {1095 if (s.test_results.skip_count > 0) {
1100 try stderr.writeAll(", ");1096 try w.writeAll(", ");
1101 try ttyconf.setColor(stderr, .yellow);1097 try stderr.setColor(.yellow);
1102 try stderr.print("{d} skip", .{s.test_results.skip_count});1098 try w.print("{d} skip", .{s.test_results.skip_count});
1103 try ttyconf.setColor(stderr, .reset);1099 try stderr.setColor(.reset);
1104 if (dim) try ttyconf.setColor(stderr, .dim);1100 if (dim) try stderr.setColor(.dim);
1105 }1101 }
1106 if (s.test_results.fail_count > 0) {1102 if (s.test_results.fail_count > 0) {
1107 try stderr.writeAll(", ");1103 try w.writeAll(", ");
1108 try ttyconf.setColor(stderr, .red);1104 try stderr.setColor(.red);
1109 try stderr.print("{d} fail", .{s.test_results.fail_count});1105 try w.print("{d} fail", .{s.test_results.fail_count});
1110 try ttyconf.setColor(stderr, .reset);1106 try stderr.setColor(.reset);
1111 if (dim) try ttyconf.setColor(stderr, .dim);1107 if (dim) try stderr.setColor(.dim);
1112 }1108 }
1113 if (s.test_results.crash_count > 0) {1109 if (s.test_results.crash_count > 0) {
1114 try stderr.writeAll(", ");1110 try w.writeAll(", ");
1115 try ttyconf.setColor(stderr, .red);1111 try stderr.setColor(.red);
1116 try stderr.print("{d} crash", .{s.test_results.crash_count});1112 try w.print("{d} crash", .{s.test_results.crash_count});
1117 try ttyconf.setColor(stderr, .reset);1113 try stderr.setColor(.reset);
1118 if (dim) try ttyconf.setColor(stderr, .dim);1114 if (dim) try stderr.setColor(.dim);
1119 }1115 }
1120 if (s.test_results.timeout_count > 0) {1116 if (s.test_results.timeout_count > 0) {
1121 try stderr.writeAll(", ");1117 try w.writeAll(", ");
1122 try ttyconf.setColor(stderr, .red);1118 try stderr.setColor(.red);
1123 try stderr.print("{d} timeout", .{s.test_results.timeout_count});1119 try w.print("{d} timeout", .{s.test_results.timeout_count});
1124 try ttyconf.setColor(stderr, .reset);1120 try stderr.setColor(.reset);
1125 if (dim) try ttyconf.setColor(stderr, .dim);1121 if (dim) try stderr.setColor(.dim);
1126 }1122 }
1127 try stderr.print(" ({d} total)", .{s.test_results.test_count});1123 try w.print(" ({d} total)", .{s.test_results.test_count});
11281124
1129 // Memory leaks are intentionally written after the total, because is isn't a test *status*,1125 // Memory leaks are intentionally written after the total, because is isn't a test *status*,
1130 // but just a flag that any tests -- even passed ones -- can have. We also use a different1126 // but just a flag that any tests -- even passed ones -- can have. We also use a different
1131 // separator, so it looks like:1127 // separator, so it looks like:
1132 // 2 pass, 1 skip, 2 fail (5 total); 2 leaks1128 // 2 pass, 1 skip, 2 fail (5 total); 2 leaks
1133 if (s.test_results.leak_count > 0) {1129 if (s.test_results.leak_count > 0) {
1134 try stderr.writeAll("; ");1130 try w.writeAll("; ");
1135 try ttyconf.setColor(stderr, .red);1131 try stderr.setColor(.red);
1136 try stderr.print("{d} leaks", .{s.test_results.leak_count});1132 try w.print("{d} leaks", .{s.test_results.leak_count});
1137 try ttyconf.setColor(stderr, .reset);1133 try stderr.setColor(.reset);
1138 if (dim) try ttyconf.setColor(stderr, .dim);1134 if (dim) try stderr.setColor(.dim);
1139 }1135 }
11401136
1141 // It's usually not helpful to know how many error logs there were because they tend to1137 // It's usually not helpful to know how many error logs there were because they tend to
...@@ -1148,21 +1144,21 @@ fn printStepFailure(...@@ -1148,21 +1144,21 @@ fn printStepFailure(
1148 break :show alt_results.isSuccess();1144 break :show alt_results.isSuccess();
1149 };1145 };
1150 if (show_err_logs) {1146 if (show_err_logs) {
1151 try stderr.writeAll("; ");1147 try w.writeAll("; ");
1152 try ttyconf.setColor(stderr, .red);1148 try stderr.setColor(.red);
1153 try stderr.print("{d} error logs", .{s.test_results.log_err_count});1149 try w.print("{d} error logs", .{s.test_results.log_err_count});
1154 try ttyconf.setColor(stderr, .reset);1150 try stderr.setColor(.reset);
1155 if (dim) try ttyconf.setColor(stderr, .dim);1151 if (dim) try stderr.setColor(.dim);
1156 }1152 }
11571153
1158 try stderr.writeAll("\n");1154 try w.writeAll("\n");
1159 } else if (s.result_error_msgs.items.len > 0) {1155 } else if (s.result_error_msgs.items.len > 0) {
1160 try ttyconf.setColor(stderr, .red);1156 try stderr.setColor(.red);
1161 try stderr.writeAll(" failure\n");1157 try w.writeAll(" failure\n");
1162 } else {1158 } else {
1163 assert(s.result_stderr.len > 0);1159 assert(s.result_stderr.len > 0);
1164 try ttyconf.setColor(stderr, .red);1160 try stderr.setColor(.red);
1165 try stderr.writeAll(" stderr\n");1161 try w.writeAll(" w\n");
1166 }1162 }
1167}1163}
11681164
...@@ -1170,11 +1166,11 @@ fn printTreeStep(...@@ -1170,11 +1166,11 @@ fn printTreeStep(
1170 b: *std.Build,1166 b: *std.Build,
1171 s: *Step,1167 s: *Step,
1172 run: *const Run,1168 run: *const Run,
1173 stderr: *Writer,1169 stderr: Io.Terminal,
1174 ttyconf: tty.Config,
1175 parent_node: *PrintNode,1170 parent_node: *PrintNode,
1176 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),1171 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
1177) !void {1172) !void {
1173 const writer = stderr.writer;
1178 const first = step_stack.swapRemove(s);1174 const first = step_stack.swapRemove(s);
1179 const summary = run.summary;1175 const summary = run.summary;
1180 const skip = switch (summary) {1176 const skip = switch (summary) {
...@@ -1184,26 +1180,26 @@ fn printTreeStep(...@@ -1184,26 +1180,26 @@ fn printTreeStep(
1184 .failures => s.state == .success,1180 .failures => s.state == .success,
1185 };1181 };
1186 if (skip) return;1182 if (skip) return;
1187 try printPrefix(parent_node, stderr, ttyconf);1183 try printPrefix(parent_node, stderr);
11881184
1189 if (parent_node.parent != null) {1185 if (parent_node.parent != null) {
1190 if (parent_node.last) {1186 if (parent_node.last) {
1191 try printChildNodePrefix(stderr, ttyconf);1187 try printChildNodePrefix(stderr);
1192 } else {1188 } else {
1193 try stderr.writeAll(switch (ttyconf) {1189 try writer.writeAll(switch (stderr.mode) {
1194 .no_color, .windows_api => "+- ",
1195 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─1190 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
1191 else => "+- ",
1196 });1192 });
1197 }1193 }
1198 }1194 }
11991195
1200 if (!first) try ttyconf.setColor(stderr, .dim);1196 if (!first) try stderr.setColor(.dim);
12011197
1202 // dep_prefix omitted here because it is redundant with the tree.1198 // dep_prefix omitted here because it is redundant with the tree.
1203 try stderr.writeAll(s.name);1199 try writer.writeAll(s.name);
12041200
1205 if (first) {1201 if (first) {
1206 try printStepStatus(s, stderr, ttyconf, run);1202 try printStepStatus(s, stderr, run);
12071203
1208 const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: {1204 const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: {
1209 var i: usize = s.dependencies.items.len;1205 var i: usize = s.dependencies.items.len;
...@@ -1225,17 +1221,17 @@ fn printTreeStep(...@@ -1225,17 +1221,17 @@ fn printTreeStep(
1225 .parent = parent_node,1221 .parent = parent_node,
1226 .last = i == last_index,1222 .last = i == last_index,
1227 };1223 };
1228 try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack);1224 try printTreeStep(b, dep, run, stderr, &print_node, step_stack);
1229 }1225 }
1230 } else {1226 } else {
1231 if (s.dependencies.items.len == 0) {1227 if (s.dependencies.items.len == 0) {
1232 try stderr.writeAll(" (reused)\n");1228 try writer.writeAll(" (reused)\n");
1233 } else {1229 } else {
1234 try stderr.print(" (+{d} more reused dependencies)\n", .{1230 try writer.print(" (+{d} more reused dependencies)\n", .{
1235 s.dependencies.items.len,1231 s.dependencies.items.len,
1236 });1232 });
1237 }1233 }
1238 try ttyconf.setColor(stderr, .reset);1234 try stderr.setColor(.reset);
1239 }1235 }
1240}1236}
12411237
...@@ -1306,7 +1302,8 @@ fn workerMakeOneStep(...@@ -1306,7 +1302,8 @@ fn workerMakeOneStep(
1306 prog_node: std.Progress.Node,1302 prog_node: std.Progress.Node,
1307 run: *Run,1303 run: *Run,
1308) void {1304) void {
1309 const io = b.graph.io;1305 const graph = b.graph;
1306 const io = graph.io;
1310 const gpa = run.gpa;1307 const gpa = run.gpa;
13111308
1312 // First, check the conditions for running this step. If they are not met,1309 // First, check the conditions for running this step. If they are not met,
...@@ -1366,7 +1363,6 @@ fn workerMakeOneStep(...@@ -1366,7 +1363,6 @@ fn workerMakeOneStep(
1366 .progress_node = sub_prog_node,1363 .progress_node = sub_prog_node,
1367 .watch = run.watch,1364 .watch = run.watch,
1368 .web_server = if (run.web_server) |*ws| ws else null,1365 .web_server = if (run.web_server) |*ws| ws else null,
1369 .ttyconf = run.ttyconf,
1370 .unit_test_timeout_ns = run.unit_test_timeout_ns,1366 .unit_test_timeout_ns = run.unit_test_timeout_ns,
1371 .gpa = gpa,1367 .gpa = gpa,
1372 });1368 });
...@@ -1376,10 +1372,11 @@ fn workerMakeOneStep(...@@ -1376,10 +1372,11 @@ fn workerMakeOneStep(
1376 const show_error_msgs = s.result_error_msgs.items.len > 0;1372 const show_error_msgs = s.result_error_msgs.items.len > 0;
1377 const show_stderr = s.result_stderr.len > 0;1373 const show_stderr = s.result_stderr.len > 0;
1378 if (show_error_msgs or show_compile_errors or show_stderr) {1374 if (show_error_msgs or show_compile_errors or show_stderr) {
1379 const bw, _ = std.debug.lockStderrWriter(&stdio_buffer_allocation);1375 const stderr = io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode) catch |err| switch (err) {
1380 defer std.debug.unlockStderrWriter();1376 error.Canceled => return,
1381 const ttyconf = run.ttyconf;1377 };
1382 printErrorMessages(gpa, s, .{}, bw, ttyconf, run.error_style, run.multiline_errors) catch {};1378 defer io.unlockStderr();
1379 printErrorMessages(gpa, s, .{}, stderr.terminal(), run.error_style, run.multiline_errors) catch {};
1383 }1380 }
13841381
1385 handle_result: {1382 handle_result: {
...@@ -1446,11 +1443,11 @@ pub fn printErrorMessages(...@@ -1446,11 +1443,11 @@ pub fn printErrorMessages(
1446 gpa: Allocator,1443 gpa: Allocator,
1447 failing_step: *Step,1444 failing_step: *Step,
1448 options: std.zig.ErrorBundle.RenderOptions,1445 options: std.zig.ErrorBundle.RenderOptions,
1449 stderr: *Writer,1446 stderr: Io.Terminal,
1450 ttyconf: tty.Config,
1451 error_style: ErrorStyle,1447 error_style: ErrorStyle,
1452 multiline_errors: MultilineErrors,1448 multiline_errors: MultilineErrors,
1453) !void {1449) !void {
1450 const writer = stderr.writer;
1454 if (error_style.verboseContext()) {1451 if (error_style.verboseContext()) {
1455 // Provide context for where these error messages are coming from by1452 // Provide context for where these error messages are coming from by
1456 // printing the corresponding Step subtree.1453 // printing the corresponding Step subtree.
...@@ -1462,70 +1459,70 @@ pub fn printErrorMessages(...@@ -1462,70 +1459,70 @@ pub fn printErrorMessages(
1462 }1459 }
14631460
1464 // Now, `step_stack` has the subtree that we want to print, in reverse order.1461 // 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);
1466 var indent: usize = 0;1463 var indent: usize = 0;
1467 while (step_stack.pop()) |s| : (indent += 1) {1464 while (step_stack.pop()) |s| : (indent += 1) {
1468 if (indent > 0) {1465 if (indent > 0) {
1469 try stderr.splatByteAll(' ', (indent - 1) * 3);1466 try writer.splatByteAll(' ', (indent - 1) * 3);
1470 try printChildNodePrefix(stderr, ttyconf);1467 try printChildNodePrefix(stderr);
1471 }1468 }
14721469
1473 try stderr.writeAll(s.name);1470 try writer.writeAll(s.name);
14741471
1475 if (s == failing_step) {1472 if (s == failing_step) {
1476 try printStepFailure(s, stderr, ttyconf, true);1473 try printStepFailure(s, stderr, true);
1477 } else {1474 } else {
1478 try stderr.writeAll("\n");1475 try writer.writeAll("\n");
1479 }1476 }
1480 }1477 }
1481 try ttyconf.setColor(stderr, .reset);1478 try stderr.setColor(.reset);
1482 } else {1479 } else {
1483 // Just print the failing step itself.1480 // Just print the failing step itself.
1484 try ttyconf.setColor(stderr, .dim);1481 try stderr.setColor(.dim);
1485 try stderr.writeAll(failing_step.name);1482 try writer.writeAll(failing_step.name);
1486 try printStepFailure(failing_step, stderr, ttyconf, true);1483 try printStepFailure(failing_step, stderr, true);
1487 try ttyconf.setColor(stderr, .reset);1484 try stderr.setColor(.reset);
1488 }1485 }
14891486
1490 if (failing_step.result_stderr.len > 0) {1487 if (failing_step.result_stderr.len > 0) {
1491 try stderr.writeAll(failing_step.result_stderr);1488 try writer.writeAll(failing_step.result_stderr);
1492 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {1489 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1493 try stderr.writeAll("\n");1490 try writer.writeAll("\n");
1494 }1491 }
1495 }1492 }
14961493
1497 try failing_step.result_error_bundle.renderToWriter(options, stderr, ttyconf);1494 try failing_step.result_error_bundle.renderToTerminal(options, stderr);
14981495
1499 for (failing_step.result_error_msgs.items) |msg| {1496 for (failing_step.result_error_msgs.items) |msg| {
1500 try ttyconf.setColor(stderr, .red);1497 try stderr.setColor(.red);
1501 try stderr.writeAll("error:");1498 try writer.writeAll("error:");
1502 try ttyconf.setColor(stderr, .reset);1499 try stderr.setColor(.reset);
1503 if (std.mem.indexOfScalar(u8, msg, '\n') == null) {1500 if (std.mem.indexOfScalar(u8, msg, '\n') == null) {
1504 try stderr.print(" {s}\n", .{msg});1501 try writer.print(" {s}\n", .{msg});
1505 } else switch (multiline_errors) {1502 } else switch (multiline_errors) {
1506 .indent => {1503 .indent => {
1507 var it = std.mem.splitScalar(u8, msg, '\n');1504 var it = std.mem.splitScalar(u8, msg, '\n');
1508 try stderr.print(" {s}\n", .{it.first()});1505 try writer.print(" {s}\n", .{it.first()});
1509 while (it.next()) |line| {1506 while (it.next()) |line| {
1510 try stderr.print(" {s}\n", .{line});1507 try writer.print(" {s}\n", .{line});
1511 }1508 }
1512 },1509 },
1513 .newline => try stderr.print("\n{s}\n", .{msg}),1510 .newline => try writer.print("\n{s}\n", .{msg}),
1514 .none => try stderr.print(" {s}\n", .{msg}),1511 .none => try writer.print(" {s}\n", .{msg}),
1515 }1512 }
1516 }1513 }
15171514
1518 if (error_style.verboseContext()) {1515 if (error_style.verboseContext()) {
1519 if (failing_step.result_failed_command) |cmd_str| {1516 if (failing_step.result_failed_command) |cmd_str| {
1520 try ttyconf.setColor(stderr, .red);1517 try stderr.setColor(.red);
1521 try stderr.writeAll("failed command: ");1518 try writer.writeAll("failed command: ");
1522 try ttyconf.setColor(stderr, .reset);1519 try stderr.setColor(.reset);
1523 try stderr.writeAll(cmd_str);1520 try writer.writeAll(cmd_str);
1524 try stderr.writeByte('\n');1521 try writer.writeByte('\n');
1525 }1522 }
1526 }1523 }
15271524
1528 try stderr.writeByte('\n');1525 try writer.writeByte('\n');
1529}1526}
15301527
1531fn printSteps(builder: *std.Build, w: *Writer) !void {1528fn printSteps(builder: *std.Build, w: *Writer) !void {
...@@ -1843,9 +1840,9 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {...@@ -1843,9 +1840,9 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {
1843}1840}
18441841
1845var stdio_buffer_allocation: [256]u8 = undefined;1842var 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 {1845fn initStdoutWriter(io: Io) *Writer {
1849 stdout_writer_allocation = std.fs.File.stdout().writerStreaming(&stdio_buffer_allocation);1846 stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation);
1850 return &stdout_writer_allocation.interface;1847 return &stdout_writer_allocation.interface;
1851}1848}
lib/compiler/libc.zig+10-10
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const mem = std.mem;3const mem = std.mem;
3const LibCInstallation = std.zig.LibCInstallation;4const LibCInstallation = std.zig.LibCInstallation;
45
...@@ -29,7 +30,7 @@ pub fn main() !void {...@@ -29,7 +30,7 @@ pub fn main() !void {
29 const arena = arena_instance.allocator();30 const arena = arena_instance.allocator();
30 const gpa = arena;31 const gpa = arena;
3132
32 var threaded: std.Io.Threaded = .init(gpa);33 var threaded: std.Io.Threaded = .init(gpa, .{});
33 defer threaded.deinit();34 defer threaded.deinit();
34 const io = threaded.io();35 const io = threaded.io();
3536
...@@ -39,7 +40,7 @@ pub fn main() !void {...@@ -39,7 +40,7 @@ pub fn main() !void {
39 var input_file: ?[]const u8 = null;40 var input_file: ?[]const u8 = null;
40 var target_arch_os_abi: []const u8 = "native";41 var target_arch_os_abi: []const u8 = "native";
41 var print_includes: bool = false;42 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);
43 const stdout = &stdout_writer.interface;44 const stdout = &stdout_writer.interface;
44 {45 {
45 var i: usize = 2;46 var i: usize = 2;
...@@ -49,7 +50,7 @@ pub fn main() !void {...@@ -49,7 +50,7 @@ pub fn main() !void {
49 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {50 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
50 try stdout.writeAll(usage_libc);51 try stdout.writeAll(usage_libc);
51 try stdout.flush();52 try stdout.flush();
52 return std.process.cleanExit();53 return std.process.cleanExit(io);
53 } else if (mem.eql(u8, arg, "-target")) {54 } else if (mem.eql(u8, arg, "-target")) {
54 if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});55 if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});
55 i += 1;56 i += 1;
...@@ -77,7 +78,7 @@ pub fn main() !void {...@@ -77,7 +78,7 @@ pub fn main() !void {
77 if (input_file) |libc_file| {78 if (input_file) |libc_file| {
78 const libc = try arena.create(LibCInstallation);79 const libc = try arena.create(LibCInstallation);
79 libc.* = LibCInstallation.parse(arena, libc_file, &target) catch |err| {80 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 });
81 };82 };
82 break :libc libc;83 break :libc libc;
83 } else {84 } else {
...@@ -96,7 +97,7 @@ pub fn main() !void {...@@ -96,7 +97,7 @@ pub fn main() !void {
96 libc_installation,97 libc_installation,
97 ) catch |err| {98 ) catch |err| {
98 const zig_target = try target.zigTriple(arena);99 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 });
100 };101 };
101102
102 if (libc_dirs.libc_include_dir_list.len == 0) {103 if (libc_dirs.libc_include_dir_list.len == 0) {
...@@ -109,24 +110,23 @@ pub fn main() !void {...@@ -109,24 +110,23 @@ pub fn main() !void {
109 try stdout.writeByte('\n');110 try stdout.writeByte('\n');
110 }111 }
111 try stdout.flush();112 try stdout.flush();
112 return std.process.cleanExit();113 return std.process.cleanExit(io);
113 }114 }
114115
115 if (input_file) |libc_file| {116 if (input_file) |libc_file| {
116 var libc = LibCInstallation.parse(gpa, libc_file, &target) catch |err| {117 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 });
118 };119 };
119 defer libc.deinit(gpa);120 defer libc.deinit(gpa);
120 } else {121 } else {
121 if (!target_query.canDetectLibC()) {122 if (!target_query.canDetectLibC()) {
122 fatal("unable to detect libc for non-native target", .{});123 fatal("unable to detect libc for non-native target", .{});
123 }124 }
124 var libc = LibCInstallation.findNative(.{125 var libc = LibCInstallation.findNative(gpa, io, .{
125 .allocator = gpa,
126 .verbose = true,126 .verbose = true,
127 .target = &target,127 .target = &target,
128 }) catch |err| {128 }) catch |err| {
129 fatal("unable to detect native libc: {s}", .{@errorName(err)});129 fatal("unable to detect native libc: {t}", .{err});
130 };130 };
131 defer libc.deinit(gpa);131 defer libc.deinit(gpa);
132132
lib/compiler/objcopy.zig+24-22
...@@ -1,12 +1,13 @@...@@ -1,12 +1,13 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2
2const std = @import("std");3const std = @import("std");
4const Io = std.Io;
3const mem = std.mem;5const mem = std.mem;
4const fs = std.fs;6const fs = std.fs;
5const elf = std.elf;7const elf = std.elf;
6const Allocator = std.mem.Allocator;8const Allocator = std.mem.Allocator;
7const File = std.fs.File;9const File = std.Io.File;
8const assert = std.debug.assert;10const assert = std.debug.assert;
9
10const fatal = std.process.fatal;11const fatal = std.process.fatal;
11const Server = std.zig.Server;12const Server = std.zig.Server;
1213
...@@ -24,11 +25,15 @@ pub fn main() !void {...@@ -24,11 +25,15 @@ pub fn main() !void {
24 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;25 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
25 const gpa = general_purpose_allocator.allocator();26 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
27 const args = try std.process.argsAlloc(arena);32 const args = try std.process.argsAlloc(arena);
28 return cmdObjCopy(gpa, arena, args[1..]);33 return cmdObjCopy(arena, io, args[1..]);
29}34}
3035
31fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {36fn cmdObjCopy(arena: Allocator, io: Io, args: []const []const u8) !void {
32 var i: usize = 0;37 var i: usize = 0;
33 var opt_out_fmt: ?std.Target.ObjectFormat = null;38 var opt_out_fmt: ?std.Target.ObjectFormat = null;
34 var opt_input: ?[]const u8 = null;39 var opt_input: ?[]const u8 = null;
...@@ -56,7 +61,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void...@@ -56,7 +61,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
56 fatal("unexpected positional argument: '{s}'", .{arg});61 fatal("unexpected positional argument: '{s}'", .{arg});
57 }62 }
58 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {63 } 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);
60 } else if (mem.eql(u8, arg, "-O") or mem.eql(u8, arg, "--output-target")) {65 } else if (mem.eql(u8, arg, "-O") or mem.eql(u8, arg, "--output-target")) {
61 i += 1;66 i += 1;
62 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});67 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...@@ -147,16 +152,12 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
147 const input = opt_input orelse fatal("expected input parameter", .{});152 const input = opt_input orelse fatal("expected input parameter", .{});
148 const output = opt_output orelse fatal("expected output parameter", .{});153 const output = opt_output orelse fatal("expected output parameter", .{});
149154
150 var threaded: std.Io.Threaded = .init(gpa);155 const input_file = Io.Dir.cwd().openFile(io, input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err });
151 defer threaded.deinit();156 defer input_file.close(io);
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();
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
161 const elf_hdr = std.elf.Header.read(&in.interface) catch |err| switch (err) {162 const elf_hdr = std.elf.Header.read(&in.interface) catch |err| switch (err) {
162 error.ReadFailed => fatal("unable to read {s}: {t}", .{ input, in.err.? }),163 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...@@ -177,12 +178,12 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
177 }178 }
178 };179 };
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 var output_file = try Io.Dir.cwd().createFile(io, output, .{ .permissions = permissions });
183 defer output_file.close();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
187 switch (out_fmt) {188 switch (out_fmt) {
188 .hex, .raw => {189 .hex, .raw => {
...@@ -221,8 +222,8 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void...@@ -221,8 +222,8 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
221 try out.end();222 try out.end();
222223
223 if (listen) {224 if (listen) {
224 var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer);225 var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer);
225 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);226 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
226 var server = try Server.init(.{227 var server = try Server.init(.{
227 .in = &stdin_reader.interface,228 .in = &stdin_reader.interface,
228 .out = &stdout_writer.interface,229 .out = &stdout_writer.interface,
...@@ -234,7 +235,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void...@@ -234,7 +235,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
234 const hdr = try server.receiveMessage();235 const hdr = try server.receiveMessage();
235 switch (hdr.tag) {236 switch (hdr.tag) {
236 .exit => {237 .exit => {
237 return std.process.cleanExit();238 return std.process.cleanExit(io);
238 },239 },
239 .update => {240 .update => {
240 if (seen_update) fatal("zig objcopy only supports 1 update for now", .{});241 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...@@ -249,7 +250,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
249 }250 }
250 }251 }
251 }252 }
252 return std.process.cleanExit();253 return std.process.cleanExit(io);
253}254}
254255
255const usage =256const usage =
...@@ -675,8 +676,9 @@ fn containsValidAddressRange(segments: []*BinaryElfSegment) bool {...@@ -675,8 +676,9 @@ fn containsValidAddressRange(segments: []*BinaryElfSegment) bool {
675}676}
676677
677fn padFile(out: *File.Writer, opt_size: ?u64) !void {678fn padFile(out: *File.Writer, opt_size: ?u64) !void {
679 const io = out.io;
678 const size = opt_size orelse return;680 const size = opt_size orelse return;
679 try out.file.setEndPos(size);681 try out.file.setLength(io, size);
680}682}
681683
682test "HexWriter.Record.Address has correct payload and checksum" {684test "HexWriter.Record.Address has correct payload and checksum" {
lib/compiler/reduce.zig+24-22
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const mem = std.mem;3const mem = std.mem;
3const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;5const assert = std.debug.assert;
...@@ -54,6 +55,10 @@ pub fn main() !void {...@@ -54,6 +55,10 @@ pub fn main() !void {
54 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;55 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
55 const gpa = general_purpose_allocator.allocator();56 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
57 const args = try std.process.argsAlloc(arena);62 const args = try std.process.argsAlloc(arena);
5863
59 var opt_checker_path: ?[]const u8 = null;64 var opt_checker_path: ?[]const u8 = null;
...@@ -68,9 +73,9 @@ pub fn main() !void {...@@ -68,9 +73,9 @@ pub fn main() !void {
68 const arg = args[i];73 const arg = args[i];
69 if (mem.startsWith(u8, arg, "-")) {74 if (mem.startsWith(u8, arg, "-")) {
70 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {75 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();
72 try stdout.writeAll(usage);77 try stdout.writeAll(usage);
73 return std.process.cleanExit();78 return std.process.cleanExit(io);
74 } else if (mem.eql(u8, arg, "--")) {79 } else if (mem.eql(u8, arg, "--")) {
75 argv = args[i + 1 ..];80 argv = args[i + 1 ..];
76 break;81 break;
...@@ -87,9 +92,7 @@ pub fn main() !void {...@@ -87,9 +92,7 @@ pub fn main() !void {
87 if (i >= args.len) fatal("expected 32-bit integer after {s}", .{arg});92 if (i >= args.len) fatal("expected 32-bit integer after {s}", .{arg});
88 const next_arg = args[i];93 const next_arg = args[i];
89 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {94 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
90 fatal("unable to parse seed '{s}' as 32-bit integer: {s}", .{95 fatal("unable to parse seed '{s}' as 32-bit integer: {t}", .{ next_arg, err });
91 next_arg, @errorName(err),
92 });
93 };96 };
94 } else {97 } else {
95 fatal("unrecognized parameter: '{s}'", .{arg});98 fatal("unrecognized parameter: '{s}'", .{arg});
...@@ -120,7 +123,7 @@ pub fn main() !void {...@@ -120,7 +123,7 @@ pub fn main() !void {
120 var astgen_input: std.Io.Writer.Allocating = .init(gpa);123 var astgen_input: std.Io.Writer.Allocating = .init(gpa);
121 defer astgen_input.deinit();124 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);
124 defer {127 defer {
125 gpa.free(tree.source);128 gpa.free(tree.source);
126 tree.deinit(gpa);129 tree.deinit(gpa);
...@@ -185,7 +188,7 @@ pub fn main() !void {...@@ -185,7 +188,7 @@ pub fn main() !void {
185 std.debug.print("{s} ", .{@tagName(t)});188 std.debug.print("{s} ", .{@tagName(t)});
186 }189 }
187 std.debug.print("\n", .{});190 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
190 rendered.clearRetainingCapacity();193 rendered.clearRetainingCapacity();
191 try tree.render(gpa, &rendered.writer, fixups);194 try tree.render(gpa, &rendered.writer, fixups);
...@@ -232,16 +235,16 @@ pub fn main() !void {...@@ -232,16 +235,16 @@ pub fn main() !void {
232 }235 }
233 }236 }
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() });
236 // std.debug.print("trying this code:\n{s}\n", .{rendered.items});239 // std.debug.print("trying this code:\n{s}\n", .{rendered.items});
237240
238 const interestingness = try runCheck(arena, interestingness_argv.items);241 const interestingness = try runCheck(arena, interestingness_argv.items);
239 std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{242 std.debug.print("{d} random transformations: {t}. {d}/{d}\n", .{
240 subset_size, @tagName(interestingness), start_index, transformations.items.len,243 subset_size, interestingness, start_index, transformations.items.len,
241 });244 });
242 switch (interestingness) {245 switch (interestingness) {
243 .interesting => {246 .interesting => {
244 const new_tree = try parse(gpa, root_source_file_path);247 const new_tree = try parse(gpa, io, root_source_file_path);
245 gpa.free(tree.source);248 gpa.free(tree.source);
246 tree.deinit(gpa);249 tree.deinit(gpa);
247 tree = new_tree;250 tree = new_tree;
...@@ -273,12 +276,12 @@ pub fn main() !void {...@@ -273,12 +276,12 @@ pub fn main() !void {
273 fixups.clearRetainingCapacity();276 fixups.clearRetainingCapacity();
274 rendered.clearRetainingCapacity();277 rendered.clearRetainingCapacity();
275 try tree.render(gpa, &rendered.writer, fixups);278 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);
279 }282 }
280 std.debug.print("no more transformations found\n", .{});283 std.debug.print("no more transformations found\n", .{});
281 return std.process.cleanExit();284 return std.process.cleanExit(io);
282}285}
283286
284fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) void {287fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) void {
...@@ -302,11 +305,8 @@ fn termToInteresting(term: std.process.Child.Term) Interestingness {...@@ -302,11 +305,8 @@ fn termToInteresting(term: std.process.Child.Term) Interestingness {
302 };305 };
303}306}
304307
305fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness {308fn runCheck(arena: Allocator, io: Io, argv: []const []const u8) !Interestingness {
306 const result = try std.process.Child.run(.{309 const result = try std.process.Child.run(arena, io, .{ .argv = argv });
307 .allocator = arena,
308 .argv = argv,
309 });
310 if (result.stderr.len != 0)310 if (result.stderr.len != 0)
311 std.debug.print("{s}", .{result.stderr});311 std.debug.print("{s}", .{result.stderr});
312 return termToInteresting(result.term);312 return termToInteresting(result.term);
...@@ -315,6 +315,7 @@ fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness...@@ -315,6 +315,7 @@ fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness
315fn transformationsToFixups(315fn transformationsToFixups(
316 gpa: Allocator,316 gpa: Allocator,
317 arena: Allocator,317 arena: Allocator,
318 io: Io,
318 root_source_file_path: []const u8,319 root_source_file_path: []const u8,
319 transforms: []const Walk.Transformation,320 transforms: []const Walk.Transformation,
320 fixups: *Ast.Render.Fixups,321 fixups: *Ast.Render.Fixups,
...@@ -352,7 +353,7 @@ fn transformationsToFixups(...@@ -352,7 +353,7 @@ fn transformationsToFixups(
352 inline_imported_file.imported_string,353 inline_imported_file.imported_string,
353 });354 });
354 defer gpa.free(full_imported_path);355 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);
356 defer {357 defer {
357 gpa.free(other_file_ast.source);358 gpa.free(other_file_ast.source);
358 other_file_ast.deinit(gpa);359 other_file_ast.deinit(gpa);
...@@ -396,8 +397,9 @@ fn transformationsToFixups(...@@ -396,8 +397,9 @@ fn transformationsToFixups(
396 };397 };
397}398}
398399
399fn parse(gpa: Allocator, file_path: []const u8) !Ast {400fn parse(gpa: Allocator, io: Io, file_path: []const u8) !Ast {
400 const source_code = std.fs.cwd().readFileAllocOptions(401 const source_code = Io.Dir.cwd().readFileAllocOptions(
402 io,
401 file_path,403 file_path,
402 gpa,404 gpa,
403 .limited(std.math.maxInt(u32)),405 .limited(std.math.maxInt(u32)),
lib/compiler/resinator/cli.zig+43-39
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const code_pages = @import("code_pages.zig");3const code_pages = @import("code_pages.zig");
3const SupportedCodePage = code_pages.SupportedCodePage;4const SupportedCodePage = code_pages.SupportedCodePage;
4const lang = @import("lang.zig");5const lang = @import("lang.zig");
...@@ -124,15 +125,15 @@ pub const Diagnostics = struct {...@@ -124,15 +125,15 @@ pub const Diagnostics = struct {
124 try self.errors.append(self.allocator, error_details);125 try self.errors.append(self.allocator, error_details);
125 }126 }
126127
127 pub fn renderToStdErr(self: *Diagnostics, args: []const []const u8) void {128 pub fn renderToStderr(self: *Diagnostics, io: Io, args: []const []const u8) Io.Cancelable!void {
128 const stderr, const ttyconf = std.debug.lockStderrWriter(&.{});129 const stderr = try io.lockStderr(&.{}, null);
129 defer std.debug.unlockStderrWriter();130 defer io.unlockStderr();
130 self.renderToWriter(args, stderr, ttyconf) catch return;131 self.renderToWriter(args, stderr.terminal()) catch return;
131 }132 }
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 {
134 for (self.errors.items) |err_details| {135 for (self.errors.items) |err_details| {
135 try renderErrorMessage(writer, config, err_details, args);136 try renderErrorMessage(t, err_details, args);
136 }137 }
137 }138 }
138139
...@@ -169,7 +170,7 @@ pub const Options = struct {...@@ -169,7 +170,7 @@ pub const Options = struct {
169 coff_options: cvtres.CoffOptions = .{},170 coff_options: cvtres.CoffOptions = .{},
170171
171 pub const IoSource = union(enum) {172 pub const IoSource = union(enum) {
172 stdio: std.fs.File,173 stdio: Io.File,
173 filename: []const u8,174 filename: []const u8,
174 };175 };
175 pub const AutoIncludes = enum { any, msvc, gnu, none };176 pub const AutoIncludes = enum { any, msvc, gnu, none };
...@@ -249,13 +250,13 @@ pub const Options = struct {...@@ -249,13 +250,13 @@ pub const Options = struct {
249 /// worlds' situation where we'll be compatible with most use-cases250 /// worlds' situation where we'll be compatible with most use-cases
250 /// of the .rc extension being omitted from the CLI args, but still251 /// of the .rc extension being omitted from the CLI args, but still
251 /// work fine if the file itself does not have an extension.252 /// 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 {
253 switch (options.input_source) {254 switch (options.input_source) {
254 .stdio => return,255 .stdio => return,
255 .filename => {},256 .filename => {},
256 }257 }
257 if (options.input_format == .rc and std.fs.path.extension(options.input_source.filename).len == 0) {258 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) {
259 error.FileNotFound => {260 error.FileNotFound => {
260 var filename_bytes = try options.allocator.alloc(u8, options.input_source.filename.len + 3);261 var filename_bytes = try options.allocator.alloc(u8, options.input_source.filename.len + 3);
261 @memcpy(filename_bytes[0..options.input_source.filename.len], options.input_source.filename);262 @memcpy(filename_bytes[0..options.input_source.filename.len], options.input_source.filename);
...@@ -418,7 +419,7 @@ pub const Arg = struct {...@@ -418,7 +419,7 @@ pub const Arg = struct {
418 };419 };
419 }420 }
420421
421 pub fn looksLikeFilepath(self: Arg) bool {422 pub fn looksLikeFilepath(self: Arg, io: Io) bool {
422 const meets_min_requirements = self.prefix == .slash and isSupportedInputExtension(std.fs.path.extension(self.full));423 const meets_min_requirements = self.prefix == .slash and isSupportedInputExtension(std.fs.path.extension(self.full));
423 if (!meets_min_requirements) return false;424 if (!meets_min_requirements) return false;
424425
...@@ -437,7 +438,7 @@ pub const Arg = struct {...@@ -437,7 +438,7 @@ pub const Arg = struct {
437 // It's still possible for a file path to look like a /fo option but not actually438 // It's still possible for a file path to look like a /fo option but not actually
438 // be one, e.g. `/foo/bar.rc`. As a last ditch effort to reduce false negatives,439 // be one, e.g. `/foo/bar.rc`. As a last ditch effort to reduce false negatives,
439 // check if the file path exists and, if so, then we ignore the 'could be /fo option'-ness440 // 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;
441 return true;442 return true;
442 }443 }
443444
...@@ -489,7 +490,7 @@ pub const ParseError = error{ParseError} || Allocator.Error;...@@ -489,7 +490,7 @@ pub const ParseError = error{ParseError} || Allocator.Error;
489490
490/// Note: Does not run `Options.maybeAppendRC` automatically. If that behavior is desired,491/// Note: Does not run `Options.maybeAppendRC` automatically. If that behavior is desired,
491/// it must be called separately.492/// 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 {
493 var options = Options{ .allocator = allocator };494 var options = Options{ .allocator = allocator };
494 errdefer options.deinit();495 errdefer options.deinit();
495496
...@@ -529,7 +530,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn...@@ -529,7 +530,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
529 }530 }
530531
531 const args_remaining = args.len - arg_i;532 const args_remaining = args.len - arg_i;
532 if (args_remaining <= 2 and arg.looksLikeFilepath()) {533 if (args_remaining <= 2 and arg.looksLikeFilepath(io)) {
533 var err_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = true, .arg_index = arg_i };534 var err_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = true, .arg_index = arg_i };
534 try err_details.msg.appendSlice(allocator, "this argument was inferred to be a filepath, so argument parsing was terminated");535 try err_details.msg.appendSlice(allocator, "this argument was inferred to be a filepath, so argument parsing was terminated");
535 try diagnostics.append(err_details);536 try diagnostics.append(err_details);
...@@ -1343,41 +1344,42 @@ test parsePercent {...@@ -1343,41 +1344,42 @@ test parsePercent {
1343 try std.testing.expectError(error.InvalidFormat, parsePercent("~1"));1344 try std.testing.expectError(error.InvalidFormat, parsePercent("~1"));
1344}1345}
13451346
1346pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err_details: Diagnostics.ErrorDetails, args: []const []const u8) !void {1347pub fn renderErrorMessage(t: Io.Terminal, err_details: Diagnostics.ErrorDetails, args: []const []const u8) !void {
1347 try config.setColor(writer, .dim);1348 const writer = t.writer;
1349 try t.setColor(.dim);
1348 try writer.writeAll("<cli>");1350 try writer.writeAll("<cli>");
1349 try config.setColor(writer, .reset);1351 try t.setColor(.reset);
1350 try config.setColor(writer, .bold);1352 try t.setColor(.bold);
1351 try writer.writeAll(": ");1353 try writer.writeAll(": ");
1352 switch (err_details.type) {1354 switch (err_details.type) {
1353 .err => {1355 .err => {
1354 try config.setColor(writer, .red);1356 try t.setColor(.red);
1355 try writer.writeAll("error: ");1357 try writer.writeAll("error: ");
1356 },1358 },
1357 .warning => {1359 .warning => {
1358 try config.setColor(writer, .yellow);1360 try t.setColor(.yellow);
1359 try writer.writeAll("warning: ");1361 try writer.writeAll("warning: ");
1360 },1362 },
1361 .note => {1363 .note => {
1362 try config.setColor(writer, .cyan);1364 try t.setColor(.cyan);
1363 try writer.writeAll("note: ");1365 try writer.writeAll("note: ");
1364 },1366 },
1365 }1367 }
1366 try config.setColor(writer, .reset);1368 try t.setColor(.reset);
1367 try config.setColor(writer, .bold);1369 try t.setColor(.bold);
1368 try writer.writeAll(err_details.msg.items);1370 try writer.writeAll(err_details.msg.items);
1369 try writer.writeByte('\n');1371 try writer.writeByte('\n');
1370 try config.setColor(writer, .reset);1372 try t.setColor(.reset);
13711373
1372 if (!err_details.print_args) {1374 if (!err_details.print_args) {
1373 try writer.writeByte('\n');1375 try writer.writeByte('\n');
1374 return;1376 return;
1375 }1377 }
13761378
1377 try config.setColor(writer, .dim);1379 try t.setColor(.dim);
1378 const prefix = " ... ";1380 const prefix = " ... ";
1379 try writer.writeAll(prefix);1381 try writer.writeAll(prefix);
1380 try config.setColor(writer, .reset);1382 try t.setColor(.reset);
13811383
1382 const arg_with_name = args[err_details.arg_index];1384 const arg_with_name = args[err_details.arg_index];
1383 const prefix_slice = arg_with_name[0..err_details.arg_span.prefix_len];1385 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...@@ -1388,15 +1390,15 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err
13881390
1389 try writer.writeAll(prefix_slice);1391 try writer.writeAll(prefix_slice);
1390 if (before_name_slice.len > 0) {1392 if (before_name_slice.len > 0) {
1391 try config.setColor(writer, .dim);1393 try t.setColor(.dim);
1392 try writer.writeAll(before_name_slice);1394 try writer.writeAll(before_name_slice);
1393 try config.setColor(writer, .reset);1395 try t.setColor(.reset);
1394 }1396 }
1395 try writer.writeAll(name_slice);1397 try writer.writeAll(name_slice);
1396 if (after_name_slice.len > 0) {1398 if (after_name_slice.len > 0) {
1397 try config.setColor(writer, .dim);1399 try t.setColor(.dim);
1398 try writer.writeAll(after_name_slice);1400 try writer.writeAll(after_name_slice);
1399 try config.setColor(writer, .reset);1401 try t.setColor(.reset);
1400 }1402 }
14011403
1402 var next_arg_len: usize = 0;1404 var next_arg_len: usize = 0;
...@@ -1414,13 +1416,13 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err...@@ -1414,13 +1416,13 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err
1414 if (err_details.arg_span.value_offset >= arg_with_name.len) {1416 if (err_details.arg_span.value_offset >= arg_with_name.len) {
1415 try writer.writeByte(' ');1417 try writer.writeByte(' ');
1416 }1418 }
1417 try config.setColor(writer, .dim);1419 try t.setColor(.dim);
1418 try writer.writeAll(" ...");1420 try writer.writeAll(" ...");
1419 try config.setColor(writer, .reset);1421 try t.setColor(.reset);
1420 }1422 }
1421 try writer.writeByte('\n');1423 try writer.writeByte('\n');
14221424
1423 try config.setColor(writer, .green);1425 try t.setColor(.green);
1424 try writer.splatByteAll(' ', prefix.len);1426 try writer.splatByteAll(' ', prefix.len);
1425 // Special case for when the option is *only* a prefix (e.g. invalid option: -)1427 // Special case for when the option is *only* a prefix (e.g. invalid option: -)
1426 if (err_details.arg_span.prefix_len == arg_with_name.len) {1428 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...@@ -1446,7 +1448,7 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, config: std.Io.tty.Config, err
1446 }1448 }
1447 }1449 }
1448 try writer.writeByte('\n');1450 try writer.writeByte('\n');
1449 try config.setColor(writer, .reset);1451 try t.setColor(.reset);
1450}1452}
14511453
1452fn testParse(args: []const []const u8) !Options {1454fn testParse(args: []const []const u8) !Options {
...@@ -1991,6 +1993,8 @@ test "parse: input and output formats" {...@@ -1991,6 +1993,8 @@ test "parse: input and output formats" {
1991}1993}
19921994
1993test "maybeAppendRC" {1995test "maybeAppendRC" {
1996 const io = std.testing.io;
1997
1994 var tmp = std.testing.tmpDir(.{});1998 var tmp = std.testing.tmpDir(.{});
1995 defer tmp.cleanup();1999 defer tmp.cleanup();
19962000
...@@ -2000,21 +2004,21 @@ test "maybeAppendRC" {...@@ -2000,21 +2004,21 @@ test "maybeAppendRC" {
20002004
2001 // Create the file so that it's found. In this scenario, .rc should not get2005 // Create the file so that it's found. In this scenario, .rc should not get
2002 // appended.2006 // appended.
2003 var file = try tmp.dir.createFile("foo", .{});2007 var file = try tmp.dir.createFile(io, "foo", .{});
2004 file.close();2008 file.close(io);
2005 try options.maybeAppendRC(tmp.dir);2009 try options.maybeAppendRC(io, tmp.dir);
2006 try std.testing.expectEqualStrings("foo", options.input_source.filename);2010 try std.testing.expectEqualStrings("foo", options.input_source.filename);
20072011
2008 // Now delete the file and try again. But this time change the input format2012 // Now delete the file and try again. But this time change the input format
2009 // to non-rc.2013 // to non-rc.
2010 try tmp.dir.deleteFile("foo");2014 try tmp.dir.deleteFile(io, "foo");
2011 options.input_format = .res;2015 options.input_format = .res;
2012 try options.maybeAppendRC(tmp.dir);2016 try options.maybeAppendRC(io, tmp.dir);
2013 try std.testing.expectEqualStrings("foo", options.input_source.filename);2017 try std.testing.expectEqualStrings("foo", options.input_source.filename);
20142018
2015 // Finally, reset the input format to rc. Since the verbatim name is no longer found2019 // Finally, reset the input format to rc. Since the verbatim name is no longer found
2016 // and the input filename does not have an extension, .rc should get appended.2020 // and the input filename does not have an extension, .rc should get appended.
2017 options.input_format = .rc;2021 options.input_format = .rc;
2018 try options.maybeAppendRC(tmp.dir);2022 try options.maybeAppendRC(io, tmp.dir);
2019 try std.testing.expectEqualStrings("foo.rc", options.input_source.filename);2023 try std.testing.expectEqualStrings("foo.rc", options.input_source.filename);
2020}2024}
lib/compiler/resinator/compile.zig+28-26
...@@ -34,7 +34,7 @@ const code_pages = @import("code_pages.zig");...@@ -34,7 +34,7 @@ const code_pages = @import("code_pages.zig");
34const errors = @import("errors.zig");34const errors = @import("errors.zig");
3535
36pub const CompileOptions = struct {36pub const CompileOptions = struct {
37 cwd: std.fs.Dir,37 cwd: std.Io.Dir,
38 diagnostics: *Diagnostics,38 diagnostics: *Diagnostics,
39 source_mappings: ?*SourceMappings = null,39 source_mappings: ?*SourceMappings = null,
40 /// List of paths (absolute or relative to `cwd`) for every file that the resources within the .rc file depend on.40 /// 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...@@ -96,7 +96,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io
96 var search_dirs: std.ArrayList(SearchDir) = .empty;96 var search_dirs: std.ArrayList(SearchDir) = .empty;
97 defer {97 defer {
98 for (search_dirs.items) |*search_dir| {98 for (search_dirs.items) |*search_dir| {
99 search_dir.deinit(allocator);99 search_dir.deinit(allocator, io);
100 }100 }
101 search_dirs.deinit(allocator);101 search_dirs.deinit(allocator);
102 }102 }
...@@ -106,13 +106,13 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io...@@ -106,13 +106,13 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io
106 // If dirname returns null, then the root path will be the same as106 // If dirname returns null, then the root path will be the same as
107 // the cwd so we don't need to add it as a distinct search path.107 // the cwd so we don't need to add it as a distinct search path.
108 if (std.fs.path.dirname(root_path)) |root_dir_path| {108 if (std.fs.path.dirname(root_path)) |root_dir_path| {
109 var root_dir = try options.cwd.openDir(root_dir_path, .{});109 var root_dir = try options.cwd.openDir(io, root_dir_path, .{});
110 errdefer root_dir.close();110 errdefer root_dir.close(io);
111 try search_dirs.append(allocator, .{ .dir = root_dir, .path = try allocator.dupe(u8, root_dir_path) });111 try search_dirs.append(allocator, .{ .dir = root_dir, .path = try allocator.dupe(u8, root_dir_path) });
112 }112 }
113 }113 }
114 // Re-open the passed in cwd since we want to be able to close it (std.fs.cwd() shouldn't be closed)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(".", .{}) catch |err| {115 const cwd_dir = options.cwd.openDir(io, ".", .{}) catch |err| {
116 try options.diagnostics.append(.{116 try options.diagnostics.append(.{
117 .err = .failed_to_open_cwd,117 .err = .failed_to_open_cwd,
118 .token = .{118 .token = .{
...@@ -132,19 +132,19 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io...@@ -132,19 +132,19 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io
132 };132 };
133 try search_dirs.append(allocator, .{ .dir = cwd_dir, .path = null });133 try search_dirs.append(allocator, .{ .dir = cwd_dir, .path = null });
134 for (options.extra_include_paths) |extra_include_path| {134 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 {
136 // TODO: maybe a warning that the search path is skipped?136 // TODO: maybe a warning that the search path is skipped?
137 continue;137 continue;
138 };138 };
139 errdefer dir.close();139 errdefer dir.close(io);
140 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, extra_include_path) });140 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, extra_include_path) });
141 }141 }
142 for (options.system_include_paths) |system_include_path| {142 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 {
144 // TODO: maybe a warning that the search path is skipped?144 // TODO: maybe a warning that the search path is skipped?
145 continue;145 continue;
146 };146 };
147 errdefer dir.close();147 errdefer dir.close(io);
148 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, system_include_path) });148 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, system_include_path) });
149 }149 }
150 if (!options.ignore_include_env_var) {150 if (!options.ignore_include_env_var) {
...@@ -159,8 +159,8 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io...@@ -159,8 +159,8 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io
159 };159 };
160 var it = std.mem.tokenizeScalar(u8, INCLUDE, delimiter);160 var it = std.mem.tokenizeScalar(u8, INCLUDE, delimiter);
161 while (it.next()) |search_path| {161 while (it.next()) |search_path| {
162 var dir = openSearchPathDir(options.cwd, search_path) catch continue;162 var dir = openSearchPathDir(options.cwd, io, search_path) catch continue;
163 errdefer dir.close();163 errdefer dir.close(io);
164 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, search_path) });164 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, search_path) });
165 }165 }
166 }166 }
...@@ -196,7 +196,7 @@ pub const Compiler = struct {...@@ -196,7 +196,7 @@ pub const Compiler = struct {
196 arena: Allocator,196 arena: Allocator,
197 allocator: Allocator,197 allocator: Allocator,
198 io: Io,198 io: Io,
199 cwd: std.fs.Dir,199 cwd: std.Io.Dir,
200 state: State = .{},200 state: State = .{},
201 diagnostics: *Diagnostics,201 diagnostics: *Diagnostics,
202 dependencies: ?*Dependencies,202 dependencies: ?*Dependencies,
...@@ -388,7 +388,9 @@ pub const Compiler = struct {...@@ -388,7 +388,9 @@ pub const Compiler = struct {
388 /// matching file is invalid. That is, it does not do the `cmd` PATH searching388 /// matching file is invalid. That is, it does not do the `cmd` PATH searching
389 /// thing of continuing to look for matching files until it finds a valid389 /// thing of continuing to look for matching files until it finds a valid
390 /// one if a matching file is invalid.390 /// 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
392 // If the path is absolute, then it is not resolved relative to any search394 // If the path is absolute, then it is not resolved relative to any search
393 // paths, so there's no point in checking them.395 // paths, so there's no point in checking them.
394 //396 //
...@@ -404,8 +406,8 @@ pub const Compiler = struct {...@@ -404,8 +406,8 @@ pub const Compiler = struct {
404 // `/test.bin` relative to include paths and instead only treats it as406 // `/test.bin` relative to include paths and instead only treats it as
405 // an absolute path.407 // an absolute path.
406 if (std.fs.path.isAbsolute(path)) {408 if (std.fs.path.isAbsolute(path)) {
407 const file = try utils.openFileNotDir(std.fs.cwd(), path, .{});409 const file = try utils.openFileNotDir(Io.Dir.cwd(), io, path, .{});
408 errdefer file.close();410 errdefer file.close(io);
409411
410 if (self.dependencies) |dependencies| {412 if (self.dependencies) |dependencies| {
411 const duped_path = try dependencies.allocator.dupe(u8, path);413 const duped_path = try dependencies.allocator.dupe(u8, path);
...@@ -414,10 +416,10 @@ pub const Compiler = struct {...@@ -414,10 +416,10 @@ pub const Compiler = struct {
414 }416 }
415 }417 }
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;
418 for (self.search_dirs) |search_dir| {420 for (self.search_dirs) |search_dir| {
419 if (utils.openFileNotDir(search_dir.dir, path, .{})) |file| {421 if (utils.openFileNotDir(search_dir.dir, io, path, .{})) |file| {
420 errdefer file.close();422 errdefer file.close(io);
421423
422 if (self.dependencies) |dependencies| {424 if (self.dependencies) |dependencies| {
423 const searched_file_path = try std.fs.path.join(dependencies.allocator, &.{425 const searched_file_path = try std.fs.path.join(dependencies.allocator, &.{
...@@ -587,7 +589,7 @@ pub const Compiler = struct {...@@ -587,7 +589,7 @@ pub const Compiler = struct {
587 });589 });
588 },590 },
589 };591 };
590 defer file_handle.close();592 defer file_handle.close(io);
591 var file_buffer: [2048]u8 = undefined;593 var file_buffer: [2048]u8 = undefined;
592 var file_reader = file_handle.reader(io, &file_buffer);594 var file_reader = file_handle.reader(io, &file_buffer);
593595
...@@ -2892,13 +2894,13 @@ pub const Compiler = struct {...@@ -2892,13 +2894,13 @@ pub const Compiler = struct {
2892 }2894 }
2893};2895};
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 {
2898 // Validate the search path to avoid possible unreachable on invalid paths,2900 // Validate the search path to avoid possible unreachable on invalid paths,
2899 // see https://github.com/ziglang/zig/issues/15607 for why this is currently necessary.2901 // see https://github.com/ziglang/zig/issues/15607 for why this is currently necessary.
2900 try validateSearchPath(path);2902 try validateSearchPath(path);
2901 return dir.openDir(path, .{});2903 return dir.openDir(io, path, .{});
2902}2904}
29032905
2904/// Very crude attempt at validating a path. This is imperfect2906/// Very crude attempt at validating a path. This is imperfect
...@@ -2927,11 +2929,11 @@ fn validateSearchPath(path: []const u8) error{BadPathName}!void {...@@ -2927,11 +2929,11 @@ fn validateSearchPath(path: []const u8) error{BadPathName}!void {
2927}2929}
29282930
2929pub const SearchDir = struct {2931pub const SearchDir = struct {
2930 dir: std.fs.Dir,2932 dir: std.Io.Dir,
2931 path: ?[]const u8,2933 path: ?[]const u8,
29322934
2933 pub fn deinit(self: *SearchDir, allocator: Allocator) void {2935 pub fn deinit(self: *SearchDir, allocator: Allocator, io: Io) void {
2934 self.dir.close();2936 self.dir.close(io);
2935 if (self.path) |path| {2937 if (self.path) |path| {
2936 allocator.free(path);2938 allocator.free(path);
2937 }2939 }
lib/compiler/resinator/errors.zig+45-45
...@@ -67,12 +67,12 @@ pub const Diagnostics = struct {...@@ -67,12 +67,12 @@ pub const Diagnostics = struct {
67 return @intCast(index);67 return @intCast(index);
68 }68 }
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 {
71 const io = self.io;71 const io = self.io;
72 const stderr, const ttyconf = std.debug.lockStderrWriter(&.{});72 const stderr = try io.lockStderr(&.{}, null);
73 defer std.debug.unlockStderrWriter();73 defer io.unlockStderr();
74 for (self.errors.items) |err_details| {74 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;
76 }76 }
77 }77 }
7878
...@@ -169,9 +169,9 @@ pub const ErrorDetails = struct {...@@ -169,9 +169,9 @@ pub const ErrorDetails = struct {
169 filename_string_index: FilenameStringIndex,169 filename_string_index: FilenameStringIndex,
170170
171 pub const FilenameStringIndex = std.meta.Int(.unsigned, 32 - @bitSizeOf(FileOpenErrorEnum));171 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 {
175 return switch (err) {175 return switch (err) {
176 inline else => |e| @field(ErrorDetails.FileOpenError.FileOpenErrorEnum, @errorName(e)),176 inline else => |e| @field(ErrorDetails.FileOpenError.FileOpenErrorEnum, @errorName(e)),
177 };177 };
...@@ -901,9 +901,8 @@ const truncated_str = "<...truncated...>";...@@ -901,9 +901,8 @@ const truncated_str = "<...truncated...>";
901901
902pub fn renderErrorMessage(902pub fn renderErrorMessage(
903 io: Io,903 io: Io,
904 writer: *std.Io.Writer,904 t: Io.Terminal,
905 tty_config: std.Io.tty.Config,905 cwd: Io.Dir,
906 cwd: std.fs.Dir,
907 err_details: ErrorDetails,906 err_details: ErrorDetails,
908 source: []const u8,907 source: []const u8,
909 strings: []const []const u8,908 strings: []const []const u8,
...@@ -927,36 +926,37 @@ pub fn renderErrorMessage(...@@ -927,36 +926,37 @@ pub fn renderErrorMessage(
927926
928 const err_line = if (corresponding_span) |span| span.start_line else err_details.token.line_number;927 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);
931 if (corresponding_file) |file| {931 if (corresponding_file) |file| {
932 try writer.writeAll(file);932 try writer.writeAll(file);
933 } else {933 } else {
934 try tty_config.setColor(writer, .dim);934 try t.setColor(.dim);
935 try writer.writeAll("<after preprocessor>");935 try writer.writeAll("<after preprocessor>");
936 try tty_config.setColor(writer, .reset);936 try t.setColor(.reset);
937 try tty_config.setColor(writer, .bold);937 try t.setColor(.bold);
938 }938 }
939 try writer.print(":{d}:{d}: ", .{ err_line, column });939 try writer.print(":{d}:{d}: ", .{ err_line, column });
940 switch (err_details.type) {940 switch (err_details.type) {
941 .err => {941 .err => {
942 try tty_config.setColor(writer, .red);942 try t.setColor(.red);
943 try writer.writeAll("error: ");943 try writer.writeAll("error: ");
944 },944 },
945 .warning => {945 .warning => {
946 try tty_config.setColor(writer, .yellow);946 try t.setColor(.yellow);
947 try writer.writeAll("warning: ");947 try writer.writeAll("warning: ");
948 },948 },
949 .note => {949 .note => {
950 try tty_config.setColor(writer, .cyan);950 try t.setColor(.cyan);
951 try writer.writeAll("note: ");951 try writer.writeAll("note: ");
952 },952 },
953 .hint => unreachable,953 .hint => unreachable,
954 }954 }
955 try tty_config.setColor(writer, .reset);955 try t.setColor(.reset);
956 try tty_config.setColor(writer, .bold);956 try t.setColor(.bold);
957 try err_details.render(writer, source, strings);957 try err_details.render(writer, source, strings);
958 try writer.writeByte('\n');958 try writer.writeByte('\n');
959 try tty_config.setColor(writer, .reset);959 try t.setColor(.reset);
960960
961 if (!err_details.print_source_line) {961 if (!err_details.print_source_line) {
962 try writer.writeByte('\n');962 try writer.writeByte('\n');
...@@ -983,20 +983,20 @@ pub fn renderErrorMessage(...@@ -983,20 +983,20 @@ pub fn renderErrorMessage(
983983
984 try writer.writeAll(source_line_for_display.line);984 try writer.writeAll(source_line_for_display.line);
985 if (source_line_for_display.truncated) {985 if (source_line_for_display.truncated) {
986 try tty_config.setColor(writer, .dim);986 try t.setColor(.dim);
987 try writer.writeAll(truncated_str);987 try writer.writeAll(truncated_str);
988 try tty_config.setColor(writer, .reset);988 try t.setColor(.reset);
989 }989 }
990 try writer.writeByte('\n');990 try writer.writeByte('\n');
991991
992 try tty_config.setColor(writer, .green);992 try t.setColor(.green);
993 const num_spaces = truncated_visual_info.point_offset - truncated_visual_info.before_len;993 const num_spaces = truncated_visual_info.point_offset - truncated_visual_info.before_len;
994 try writer.splatByteAll(' ', num_spaces);994 try writer.splatByteAll(' ', num_spaces);
995 try writer.splatByteAll('~', truncated_visual_info.before_len);995 try writer.splatByteAll('~', truncated_visual_info.before_len);
996 try writer.writeByte('^');996 try writer.writeByte('^');
997 try writer.splatByteAll('~', truncated_visual_info.after_len);997 try writer.splatByteAll('~', truncated_visual_info.after_len);
998 try writer.writeByte('\n');998 try writer.writeByte('\n');
999 try tty_config.setColor(writer, .reset);999 try t.setColor(.reset);
10001000
1001 if (corresponding_span != null and corresponding_file != null) {1001 if (corresponding_span != null and corresponding_file != null) {
1002 var worth_printing_lines: bool = true;1002 var worth_printing_lines: bool = true;
...@@ -1021,22 +1021,22 @@ pub fn renderErrorMessage(...@@ -1021,22 +1021,22 @@ pub fn renderErrorMessage(
1021 break :blk null;1021 break :blk null;
1022 },1022 },
1023 };1023 };
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);
1027 if (corresponding_file) |file| {1027 if (corresponding_file) |file| {
1028 try writer.writeAll(file);1028 try writer.writeAll(file);
1029 } else {1029 } else {
1030 try tty_config.setColor(writer, .dim);1030 try t.setColor(.dim);
1031 try writer.writeAll("<after preprocessor>");1031 try writer.writeAll("<after preprocessor>");
1032 try tty_config.setColor(writer, .reset);1032 try t.setColor(.reset);
1033 try tty_config.setColor(writer, .bold);1033 try t.setColor(.bold);
1034 }1034 }
1035 try writer.print(":{d}:{d}: ", .{ err_line, column });1035 try writer.print(":{d}:{d}: ", .{ err_line, column });
1036 try tty_config.setColor(writer, .cyan);1036 try t.setColor(.cyan);
1037 try writer.writeAll("note: ");1037 try writer.writeAll("note: ");
1038 try tty_config.setColor(writer, .reset);1038 try t.setColor(.reset);
1039 try tty_config.setColor(writer, .bold);1039 try t.setColor(.bold);
1040 try writer.writeAll("this line originated from line");1040 try writer.writeAll("this line originated from line");
1041 if (corresponding_span.?.start_line != corresponding_span.?.end_line) {1041 if (corresponding_span.?.start_line != corresponding_span.?.end_line) {
1042 try writer.print("s {}-{}", .{ corresponding_span.?.start_line, corresponding_span.?.end_line });1042 try writer.print("s {}-{}", .{ corresponding_span.?.start_line, corresponding_span.?.end_line });
...@@ -1044,7 +1044,7 @@ pub fn renderErrorMessage(...@@ -1044,7 +1044,7 @@ pub fn renderErrorMessage(
1044 try writer.print(" {}", .{corresponding_span.?.start_line});1044 try writer.print(" {}", .{corresponding_span.?.start_line});
1045 }1045 }
1046 try writer.print(" of file '{s}'\n", .{corresponding_file.?});1046 try writer.print(" of file '{s}'\n", .{corresponding_file.?});
1047 try tty_config.setColor(writer, .reset);1047 try t.setColor(.reset);
10481048
1049 if (!worth_printing_lines) return;1049 if (!worth_printing_lines) return;
10501050
...@@ -1055,21 +1055,21 @@ pub fn renderErrorMessage(...@@ -1055,21 +1055,21 @@ pub fn renderErrorMessage(
1055 }) |display_line| {1055 }) |display_line| {
1056 try writer.writeAll(display_line.line);1056 try writer.writeAll(display_line.line);
1057 if (display_line.truncated) {1057 if (display_line.truncated) {
1058 try tty_config.setColor(writer, .dim);1058 try t.setColor(.dim);
1059 try writer.writeAll(truncated_str);1059 try writer.writeAll(truncated_str);
1060 try tty_config.setColor(writer, .reset);1060 try t.setColor(.reset);
1061 }1061 }
1062 try writer.writeByte('\n');1062 try writer.writeByte('\n');
1063 }1063 }
1064 break :write_lines null;1064 break :write_lines null;
1065 };1065 };
1066 if (write_lines_err) |err| {1066 if (write_lines_err) |err| {
1067 try tty_config.setColor(writer, .red);1067 try t.setColor(.red);
1068 try writer.writeAll(" | ");1068 try writer.writeAll(" | ");
1069 try tty_config.setColor(writer, .reset);1069 try t.setColor(.reset);
1070 try tty_config.setColor(writer, .dim);1070 try t.setColor(.dim);
1071 try writer.print("unable to print line(s) from file: {s}\n", .{@errorName(err)});1071 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);
1073 }1073 }
1074 try writer.writeByte('\n');1074 try writer.writeByte('\n');
1075 }1075 }
...@@ -1094,13 +1094,13 @@ const CorrespondingLines = struct {...@@ -1094,13 +1094,13 @@ const CorrespondingLines = struct {
1094 last_byte: u8 = 0,1094 last_byte: u8 = 0,
1095 at_eof: bool = false,1095 at_eof: bool = false,
1096 span: SourceMappings.CorrespondingSpan,1096 span: SourceMappings.CorrespondingSpan,
1097 file: std.fs.File,1097 file: Io.File,
1098 file_reader: std.fs.File.Reader,1098 file_reader: Io.File.Reader,
1099 code_page: SupportedCodePage,1099 code_page: SupportedCodePage,
11001100
1101 pub fn init(1101 pub fn init(
1102 io: Io,1102 io: Io,
1103 cwd: std.fs.Dir,1103 cwd: Io.Dir,
1104 err_details: ErrorDetails,1104 err_details: ErrorDetails,
1105 line_for_comparison: []const u8,1105 line_for_comparison: []const u8,
1106 corresponding_span: SourceMappings.CorrespondingSpan,1106 corresponding_span: SourceMappings.CorrespondingSpan,
...@@ -1120,12 +1120,12 @@ const CorrespondingLines = struct {...@@ -1120,12 +1120,12 @@ const CorrespondingLines = struct {
11201120
1121 var corresponding_lines = CorrespondingLines{1121 var corresponding_lines = CorrespondingLines{
1122 .span = corresponding_span,1122 .span = corresponding_span,
1123 .file = try utils.openFileNotDir(cwd, corresponding_file, .{}),1123 .file = try utils.openFileNotDir(cwd, io, corresponding_file, .{}),
1124 .code_page = err_details.code_page,1124 .code_page = err_details.code_page,
1125 .file_reader = undefined,1125 .file_reader = undefined,
1126 };1126 };
1127 corresponding_lines.file_reader = corresponding_lines.file.reader(io, file_reader_buf);1127 corresponding_lines.file_reader = corresponding_lines.file.reader(io, file_reader_buf);
1128 errdefer corresponding_lines.deinit();1128 errdefer corresponding_lines.deinit(io);
11291129
1130 try corresponding_lines.writeLineFromStreamVerbatim(1130 try corresponding_lines.writeLineFromStreamVerbatim(
1131 &corresponding_lines.file_reader.interface,1131 &corresponding_lines.file_reader.interface,
...@@ -1221,8 +1221,8 @@ const CorrespondingLines = struct {...@@ -1221,8 +1221,8 @@ const CorrespondingLines = struct {
1221 };1221 };
1222 }1222 }
12231223
1224 pub fn deinit(self: *CorrespondingLines) void {1224 pub fn deinit(self: *CorrespondingLines, io: Io) void {
1225 self.file.close();1225 self.file.close(io);
1226 }1226 }
1227};1227};
12281228
lib/compiler/resinator/main.zig+94-94
...@@ -24,6 +24,10 @@ pub fn main() !void {...@@ -24,6 +24,10 @@ pub fn main() !void {
24 defer std.debug.assert(debug_allocator.deinit() == .ok);24 defer std.debug.assert(debug_allocator.deinit() == .ok);
25 const gpa = debug_allocator.allocator();25 const gpa = debug_allocator.allocator();
2626
27 var threaded: std.Io.Threaded = .init(gpa, .{});
28 defer threaded.deinit();
29 const io = threaded.io();
30
27 var arena_state = std.heap.ArenaAllocator.init(gpa);31 var arena_state = std.heap.ArenaAllocator.init(gpa);
28 defer arena_state.deinit();32 defer arena_state.deinit();
29 const arena = arena_state.allocator();33 const arena = arena_state.allocator();
...@@ -31,8 +35,8 @@ pub fn main() !void {...@@ -31,8 +35,8 @@ pub fn main() !void {
31 const args = try std.process.argsAlloc(arena);35 const args = try std.process.argsAlloc(arena);
3236
33 if (args.len < 2) {37 if (args.len < 2) {
34 const w, const ttyconf = std.debug.lockStderrWriter(&.{});38 const stderr = try io.lockStderr(&.{}, null);
35 try renderErrorMessage(w, ttyconf, .err, "expected zig lib dir as first argument", .{});39 try renderErrorMessage(stderr.terminal(), .err, "expected zig lib dir as first argument", .{});
36 std.process.exit(1);40 std.process.exit(1);
37 }41 }
38 const zig_lib_dir = args[1];42 const zig_lib_dir = args[1];
...@@ -45,7 +49,7 @@ pub fn main() !void {...@@ -45,7 +49,7 @@ pub fn main() !void {
45 }49 }
4650
47 var stdout_buffer: [1024]u8 = undefined;51 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);
49 const stdout = &stdout_writer.interface;53 const stdout = &stdout_writer.interface;
50 var error_handler: ErrorHandler = switch (zig_integration) {54 var error_handler: ErrorHandler = switch (zig_integration) {
51 true => .{55 true => .{
...@@ -60,35 +64,31 @@ pub fn main() !void {...@@ -60,35 +64,31 @@ pub fn main() !void {
60 var options = options: {64 var options = options: {
61 var cli_diagnostics = cli.Diagnostics.init(gpa);65 var cli_diagnostics = cli.Diagnostics.init(gpa);
62 defer cli_diagnostics.deinit();66 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) {
64 error.ParseError => {68 error.ParseError => {
65 try error_handler.emitCliDiagnostics(gpa, cli_args, &cli_diagnostics);69 try error_handler.emitCliDiagnostics(gpa, io, cli_args, &cli_diagnostics);
66 std.process.exit(1);70 std.process.exit(1);
67 },71 },
68 else => |e| return e,72 else => |e| return e,
69 };73 };
70 try options.maybeAppendRC(std.fs.cwd());74 try options.maybeAppendRC(io, Io.Dir.cwd());
7175
72 if (!zig_integration) {76 if (!zig_integration) {
73 // print any warnings/notes77 // print any warnings/notes
74 cli_diagnostics.renderToStdErr(cli_args);78 try cli_diagnostics.renderToStderr(io, cli_args);
75 // If there was something printed, then add an extra newline separator79 // If there was something printed, then add an extra newline separator
76 // so that there is a clear separation between the cli diagnostics and whatever80 // so that there is a clear separation between the cli diagnostics and whatever
77 // gets printed after81 // gets printed after
78 if (cli_diagnostics.errors.items.len > 0) {82 if (cli_diagnostics.errors.items.len > 0) {
79 const stderr, _ = std.debug.lockStderrWriter(&.{});83 const stderr = try io.lockStderr(&.{}, null);
80 defer std.debug.unlockStderrWriter();84 defer io.unlockStderr();
81 try stderr.writeByte('\n');85 try stderr.file_writer.interface.writeByte('\n');
82 }86 }
83 }87 }
84 break :options options;88 break :options options;
85 };89 };
86 defer options.deinit();90 defer options.deinit();
8791
88 var threaded: std.Io.Threaded = .init(gpa);
89 defer threaded.deinit();
90 const io = threaded.io();
91
92 if (options.print_help_and_exit) {92 if (options.print_help_and_exit) {
93 try cli.writeUsage(stdout, "zig rc");93 try cli.writeUsage(stdout, "zig rc");
94 try stdout.flush();94 try stdout.flush();
...@@ -130,18 +130,15 @@ pub fn main() !void {...@@ -130,18 +130,15 @@ pub fn main() !void {
130 var stderr_buf: [512]u8 = undefined;130 var stderr_buf: [512]u8 = undefined;
131 var diagnostics: aro.Diagnostics = .{ .output = output: {131 var diagnostics: aro.Diagnostics = .{ .output = output: {
132 if (zig_integration) break :output .{ .to_list = .{ .arena = .init(gpa) } };132 if (zig_integration) break :output .{ .to_list = .{ .arena = .init(gpa) } };
133 const w, const ttyconf = std.debug.lockStderrWriter(&stderr_buf);133 const stderr = try io.lockStderr(&stderr_buf, null);
134 break :output .{ .to_writer = .{134 break :output .{ .to_writer = stderr.terminal() };
135 .writer = w,
136 .color = ttyconf,
137 } };
138 } };135 } };
139 defer {136 defer {
140 diagnostics.deinit();137 diagnostics.deinit();
141 if (!zig_integration) std.debug.unlockStderrWriter();138 if (!zig_integration) std.debug.unlockStderr();
142 }139 }
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());
145 defer comp.deinit();142 defer comp.deinit();
146143
147 var argv: std.ArrayList([]const u8) = .empty;144 var argv: std.ArrayList([]const u8) = .empty;
...@@ -175,11 +172,11 @@ pub fn main() !void {...@@ -175,11 +172,11 @@ pub fn main() !void {
175 std.process.exit(1);172 std.process.exit(1);
176 },173 },
177 error.FileTooBig => {174 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", .{});
179 std.process.exit(1);176 std.process.exit(1);
180 },177 },
181 error.WriteFailed => {178 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", .{});
183 std.process.exit(1);180 std.process.exit(1);
184 },181 },
185 error.OutOfMemory => |e| return e,182 error.OutOfMemory => |e| return e,
...@@ -191,13 +188,13 @@ pub fn main() !void {...@@ -191,13 +188,13 @@ pub fn main() !void {
191 .stdio => |file| {188 .stdio => |file| {
192 var file_reader = file.reader(io, &.{});189 var file_reader = file.reader(io, &.{});
193 break :full_input file_reader.interface.allocRemaining(gpa, .unlimited) catch |err| {190 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)});
195 std.process.exit(1);192 std.process.exit(1);
196 };193 };
197 },194 },
198 .filename => |input_filename| {195 .filename => |input_filename| {
199 break :full_input std.fs.cwd().readFileAlloc(input_filename, gpa, .unlimited) catch |err| {196 break :full_input Io.Dir.cwd().readFileAlloc(io, 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) });197 try error_handler.emitMessage(gpa, io, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) });
201 std.process.exit(1);198 std.process.exit(1);
202 };199 };
203 },200 },
...@@ -209,10 +206,10 @@ pub fn main() !void {...@@ -209,10 +206,10 @@ pub fn main() !void {
209 if (options.preprocess == .only) {206 if (options.preprocess == .only) {
210 switch (options.output_source) {207 switch (options.output_source) {
211 .stdio => |output_file| {208 .stdio => |output_file| {
212 try output_file.writeAll(full_input);209 try output_file.writeStreamingAll(io, full_input);
213 },210 },
214 .filename => |output_filename| {211 .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 });
216 },213 },
217 }214 }
218 return;215 return;
...@@ -227,16 +224,16 @@ pub fn main() !void {...@@ -227,16 +224,16 @@ pub fn main() !void {
227 .source = .{ .memory = .empty },224 .source = .{ .memory = .empty },
228 }225 }
229 else if (options.input_format == .res)226 else if (options.input_format == .res)
230 IoStream.fromIoSource(options.input_source, .input) catch |err| {227 IoStream.fromIoSource(io, 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) });228 try error_handler.emitMessage(gpa, io, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) });
232 std.process.exit(1);229 std.process.exit(1);
233 }230 }
234 else231 else
235 IoStream.fromIoSource(options.output_source, .output) catch |err| {232 IoStream.fromIoSource(io, 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) });233 try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
237 std.process.exit(1);234 std.process.exit(1);
238 };235 };
239 defer res_stream.deinit(gpa);236 defer res_stream.deinit(gpa, io);
240237
241 const res_data = res_data: {238 const res_data = res_data: {
242 if (options.input_format != .res) {239 if (options.input_format != .res) {
...@@ -246,17 +243,17 @@ pub fn main() !void {...@@ -246,17 +243,17 @@ pub fn main() !void {
246 var mapping_results = parseAndRemoveLineCommands(gpa, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) {243 var mapping_results = parseAndRemoveLineCommands(gpa, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) {
247 error.InvalidLineCommand => {244 error.InvalidLineCommand => {
248 // TODO: Maybe output the invalid line command245 // 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", .{});
250 if (options.preprocess == .no) {247 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>\"", .{});
252 } else {249 } 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", .{});
254 }251 }
255 std.process.exit(1);252 std.process.exit(1);
256 },253 },
257 error.LineNumberOverflow => {254 error.LineNumberOverflow => {
258 // TODO: Better error message255 // 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)});
260 std.process.exit(1);257 std.process.exit(1);
261 },258 },
262 error.OutOfMemory => |e| return e,259 error.OutOfMemory => |e| return e,
...@@ -272,12 +269,12 @@ pub fn main() !void {...@@ -272,12 +269,12 @@ pub fn main() !void {
272 defer diagnostics.deinit();269 defer diagnostics.deinit();
273270
274 var output_buffer: [4096]u8 = undefined;271 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);
276 defer res_stream_writer.deinit(&res_stream.source);273 defer res_stream_writer.deinit(&res_stream.source);
277 const output_buffered_stream = res_stream_writer.interface();274 const output_buffered_stream = res_stream_writer.interface();
278275
279 compile(gpa, io, final_input, output_buffered_stream, .{276 compile(gpa, io, final_input, output_buffered_stream, .{
280 .cwd = std.fs.cwd(),277 .cwd = Io.Dir.cwd(),
281 .diagnostics = &diagnostics,278 .diagnostics = &diagnostics,
282 .source_mappings = &mapping_results.mappings,279 .source_mappings = &mapping_results.mappings,
283 .dependencies = maybe_dependencies,280 .dependencies = maybe_dependencies,
...@@ -294,9 +291,9 @@ pub fn main() !void {...@@ -294,9 +291,9 @@ pub fn main() !void {
294 .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,291 .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,
295 }) catch |err| switch (err) {292 }) catch |err| switch (err) {
296 error.ParseError, error.CompileError => {293 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);
298 // Delete the output file on error295 // Delete the output file on error
299 res_stream.cleanupAfterError();296 res_stream.cleanupAfterError(io);
300 std.process.exit(1);297 std.process.exit(1);
301 },298 },
302 else => |e| return e,299 else => |e| return e,
...@@ -306,19 +303,19 @@ pub fn main() !void {...@@ -306,19 +303,19 @@ pub fn main() !void {
306303
307 // print any warnings/notes304 // print any warnings/notes
308 if (!zig_integration) {305 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);
310 }307 }
311308
312 // write the depfile309 // write the depfile
313 if (options.depfile_path) |depfile_path| {310 if (options.depfile_path) |depfile_path| {
314 var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| {311 var depfile = Io.Dir.cwd().createFile(io, depfile_path, .{}) catch |err| {
315 try error_handler.emitMessage(gpa, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });312 try error_handler.emitMessage(gpa, io, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
316 std.process.exit(1);313 std.process.exit(1);
317 };314 };
318 defer depfile.close();315 defer depfile.close(io);
319316
320 var depfile_buffer: [1024]u8 = undefined;317 var depfile_buffer: [1024]u8 = undefined;
321 var depfile_writer = depfile.writer(&depfile_buffer);318 var depfile_writer = depfile.writer(io, &depfile_buffer);
322 switch (options.depfile_fmt) {319 switch (options.depfile_fmt) {
323 .json => {320 .json => {
324 var write_stream: std.json.Stringify = .{321 var write_stream: std.json.Stringify = .{
...@@ -340,7 +337,7 @@ pub fn main() !void {...@@ -340,7 +337,7 @@ pub fn main() !void {
340 if (options.output_format != .coff) return;337 if (options.output_format != .coff) return;
341338
342 break :res_data res_stream.source.readAll(gpa, io) catch |err| {339 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) });
344 std.process.exit(1);341 std.process.exit(1);
345 };342 };
346 };343 };
...@@ -353,27 +350,27 @@ pub fn main() !void {...@@ -353,27 +350,27 @@ pub fn main() !void {
353 var res_reader: std.Io.Reader = .fixed(res_data.bytes);350 var res_reader: std.Io.Reader = .fixed(res_data.bytes);
354 break :resources cvtres.parseRes(gpa, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| {351 break :resources cvtres.parseRes(gpa, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| {
355 // TODO: Better errors352 // 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) });
357 std.process.exit(1);354 std.process.exit(1);
358 };355 };
359 };356 };
360 defer resources.deinit();357 defer resources.deinit();
361358
362 var coff_stream = IoStream.fromIoSource(options.output_source, .output) catch |err| {359 var coff_stream = IoStream.fromIoSource(io, 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) });360 try error_handler.emitMessage(gpa, io, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
364 std.process.exit(1);361 std.process.exit(1);
365 };362 };
366 defer coff_stream.deinit(gpa);363 defer coff_stream.deinit(gpa, io);
367364
368 var coff_output_buffer: [4096]u8 = undefined;365 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
371 var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} };368 var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} };
372 cvtres.writeCoff(gpa, coff_output_buffered_stream.interface(), resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| {369 cvtres.writeCoff(gpa, coff_output_buffered_stream.interface(), resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| {
373 switch (err) {370 switch (err) {
374 error.DuplicateResource => {371 error.DuplicateResource => {
375 const duplicate_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];372 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}]", .{
377 duplicate_resource.name_value,374 duplicate_resource.name_value,
378 fmtResourceType(duplicate_resource.type_value),375 fmtResourceType(duplicate_resource.type_value),
379 duplicate_resource.language,376 duplicate_resource.language,
...@@ -381,8 +378,8 @@ pub fn main() !void {...@@ -381,8 +378,8 @@ pub fn main() !void {
381 },378 },
382 error.ResourceDataTooLong => {379 error.ResourceDataTooLong => {
383 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];380 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", .{});381 try error_handler.emitMessage(gpa, io, .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}]", .{382 try error_handler.emitMessage(gpa, io, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{
386 overflow_resource.name_value,383 overflow_resource.name_value,
387 fmtResourceType(overflow_resource.type_value),384 fmtResourceType(overflow_resource.type_value),
388 overflow_resource.language,385 overflow_resource.language,
...@@ -390,19 +387,19 @@ pub fn main() !void {...@@ -390,19 +387,19 @@ pub fn main() !void {
390 },387 },
391 error.TotalResourceDataTooLong => {388 error.TotalResourceDataTooLong => {
392 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];389 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", .{});390 try error_handler.emitMessage(gpa, io, .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}]", .{391 try error_handler.emitMessage(gpa, io, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{
395 overflow_resource.name_value,392 overflow_resource.name_value,
396 fmtResourceType(overflow_resource.type_value),393 fmtResourceType(overflow_resource.type_value),
397 overflow_resource.language,394 overflow_resource.language,
398 });395 });
399 },396 },
400 else => {397 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) });
402 },399 },
403 }400 }
404 // Delete the output file on error401 // Delete the output file on error
405 coff_stream.cleanupAfterError();402 coff_stream.cleanupAfterError(io);
406 std.process.exit(1);403 std.process.exit(1);
407 };404 };
408405
...@@ -416,58 +413,58 @@ const IoStream = struct {...@@ -416,58 +413,58 @@ const IoStream = struct {
416413
417 pub const IoDirection = enum { input, output };414 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 {
420 return .{417 return .{
421 .name = switch (source) {418 .name = switch (source) {
422 .filename => |filename| filename,419 .filename => |filename| filename,
423 .stdio => switch (io) {420 .stdio => switch (io_direction) {
424 .input => "<stdin>",421 .input => "<stdin>",
425 .output => "<stdout>",422 .output => "<stdout>",
426 },423 },
427 },424 },
428 .intermediate = false,425 .intermediate = false,
429 .source = try Source.fromIoSource(source, io),426 .source = try Source.fromIoSource(io, source, io_direction),
430 };427 };
431 }428 }
432429
433 pub fn deinit(self: *IoStream, allocator: Allocator) void {430 pub fn deinit(self: *IoStream, allocator: Allocator, io: Io) void {
434 self.source.deinit(allocator);431 self.source.deinit(allocator, io);
435 }432 }
436433
437 pub fn cleanupAfterError(self: *IoStream) void {434 pub fn cleanupAfterError(self: *IoStream, io: Io) void {
438 switch (self.source) {435 switch (self.source) {
439 .file => |file| {436 .file => |file| {
440 // Delete the output file on error437 // Delete the output file on error
441 file.close();438 file.close(io);
442 // Failing to delete is not really a big deal, so swallow any errors439 // 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 {};
444 },441 },
445 .stdio, .memory, .closed => return,442 .stdio, .memory, .closed => return,
446 }443 }
447 }444 }
448445
449 pub const Source = union(enum) {446 pub const Source = union(enum) {
450 file: std.fs.File,447 file: Io.File,
451 stdio: std.fs.File,448 stdio: Io.File,
452 memory: std.ArrayList(u8),449 memory: std.ArrayList(u8),
453 /// The source has been closed and any usage of the Source in this state is illegal (except deinit).450 /// The source has been closed and any usage of the Source in this state is illegal (except deinit).
454 closed: void,451 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 {
457 switch (source) {454 switch (source) {
458 .filename => |filename| return .{455 .filename => |filename| return .{
459 .file = switch (io) {456 .file = switch (io_direction) {
460 .input => try openFileNotDir(std.fs.cwd(), filename, .{}),457 .input => try openFileNotDir(Io.Dir.cwd(), io, filename, .{}),
461 .output => try std.fs.cwd().createFile(filename, .{}),458 .output => try Io.Dir.cwd().createFile(io, filename, .{}),
462 },459 },
463 },460 },
464 .stdio => |file| return .{ .stdio = file },461 .stdio => |file| return .{ .stdio = file },
465 }462 }
466 }463 }
467464
468 pub fn deinit(self: *Source, allocator: Allocator) void {465 pub fn deinit(self: *Source, allocator: Allocator, io: Io) void {
469 switch (self.*) {466 switch (self.*) {
470 .file => |file| file.close(),467 .file => |file| file.close(io),
471 .stdio => {},468 .stdio => {},
472 .memory => |*list| list.deinit(allocator),469 .memory => |*list| list.deinit(allocator),
473 .closed => {},470 .closed => {},
...@@ -500,10 +497,10 @@ const IoStream = struct {...@@ -500,10 +497,10 @@ const IoStream = struct {
500 }497 }
501498
502 pub const Writer = union(enum) {499 pub const Writer = union(enum) {
503 file: std.fs.File.Writer,500 file: Io.File.Writer,
504 allocating: std.Io.Writer.Allocating,501 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
508 pub fn interface(this: *@This()) *std.Io.Writer {505 pub fn interface(this: *@This()) *std.Io.Writer {
509 return switch (this.*) {506 return switch (this.*) {
...@@ -521,9 +518,9 @@ const IoStream = struct {...@@ -521,9 +518,9 @@ const IoStream = struct {
521 }518 }
522 };519 };
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 {
525 return switch (source.*) {522 return switch (source.*) {
526 .file, .stdio => |file| .{ .file = file.writer(buffer) },523 .file, .stdio => |file| .{ .file = file.writer(io, buffer) },
527 .memory => |*list| .{ .allocating = .fromArrayList(allocator, list) },524 .memory => |*list| .{ .allocating = .fromArrayList(allocator, list) },
528 .closed => unreachable,525 .closed => unreachable,
529 };526 };
...@@ -550,16 +547,16 @@ const LazyIncludePaths = struct {...@@ -550,16 +547,16 @@ const LazyIncludePaths = struct {
550 else => |e| {547 else => |e| {
551 switch (e) {548 switch (e) {
552 error.UnsupportedAutoIncludesMachineType => {549 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)});
554 },551 },
555 error.MsvcIncludesNotFound => {552 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", .{});
557 },554 },
558 error.MingwIncludesNotFound => {555 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", .{});
560 },557 },
561 }558 }
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", .{});
563 std.process.exit(1);560 std.process.exit(1);
564 },561 },
565 };562 };
...@@ -618,7 +615,7 @@ fn getIncludePaths(...@@ -618,7 +615,7 @@ fn getIncludePaths(
618 };615 };
619 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);616 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
620 const is_native_abi = target_query.isNativeAbi();617 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 {
622 if (includes == .any) {619 if (includes == .any) {
623 // fall back to mingw620 // fall back to mingw
624 includes = .gnu;621 includes = .gnu;
...@@ -644,7 +641,7 @@ fn getIncludePaths(...@@ -644,7 +641,7 @@ fn getIncludePaths(
644 };641 };
645 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);642 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
646 const is_native_abi = target_query.isNativeAbi();643 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) {
648 error.OutOfMemory => |e| return e,645 error.OutOfMemory => |e| return e,
649 else => return error.MingwIncludesNotFound,646 else => return error.MingwIncludesNotFound,
650 };647 };
...@@ -664,6 +661,7 @@ const ErrorHandler = union(enum) {...@@ -664,6 +661,7 @@ const ErrorHandler = union(enum) {
664 pub fn emitCliDiagnostics(661 pub fn emitCliDiagnostics(
665 self: *ErrorHandler,662 self: *ErrorHandler,
666 allocator: Allocator,663 allocator: Allocator,
664 io: Io,
667 args: []const []const u8,665 args: []const []const u8,
668 diagnostics: *cli.Diagnostics,666 diagnostics: *cli.Diagnostics,
669 ) !void {667 ) !void {
...@@ -674,7 +672,7 @@ const ErrorHandler = union(enum) {...@@ -674,7 +672,7 @@ const ErrorHandler = union(enum) {
674672
675 try server.serveErrorBundle(error_bundle);673 try server.serveErrorBundle(error_bundle);
676 },674 },
677 .stderr => diagnostics.renderToStdErr(args),675 .stderr => return diagnostics.renderToStderr(io, args),
678 }676 }
679 }677 }
680678
...@@ -684,6 +682,7 @@ const ErrorHandler = union(enum) {...@@ -684,6 +682,7 @@ const ErrorHandler = union(enum) {
684 fail_msg: []const u8,682 fail_msg: []const u8,
685 comp: *aro.Compilation,683 comp: *aro.Compilation,
686 ) !void {684 ) !void {
685 const io = comp.io;
687 switch (self.*) {686 switch (self.*) {
688 .server => |*server| {687 .server => |*server| {
689 var error_bundle = try compiler_util.aroDiagnosticsToErrorBundle(688 var error_bundle = try compiler_util.aroDiagnosticsToErrorBundle(
...@@ -697,9 +696,9 @@ const ErrorHandler = union(enum) {...@@ -697,9 +696,9 @@ const ErrorHandler = union(enum) {
697 },696 },
698 .stderr => {697 .stderr => {
699 // aro errors have already been emitted698 // aro errors have already been emitted
700 const stderr, const ttyconf = std.debug.lockStderrWriter(&.{});699 const stderr = try io.lockStderr(&.{}, null);
701 defer std.debug.unlockStderrWriter();700 defer io.unlockStderr();
702 try renderErrorMessage(stderr, ttyconf, .err, "{s}", .{fail_msg});701 try renderErrorMessage(stderr.terminal(), .err, "{s}", .{fail_msg});
703 },702 },
704 }703 }
705 }704 }
...@@ -707,7 +706,7 @@ const ErrorHandler = union(enum) {...@@ -707,7 +706,7 @@ const ErrorHandler = union(enum) {
707 pub fn emitDiagnostics(706 pub fn emitDiagnostics(
708 self: *ErrorHandler,707 self: *ErrorHandler,
709 allocator: Allocator,708 allocator: Allocator,
710 cwd: std.fs.Dir,709 cwd: Io.Dir,
711 source: []const u8,710 source: []const u8,
712 diagnostics: *Diagnostics,711 diagnostics: *Diagnostics,
713 mappings: SourceMappings,712 mappings: SourceMappings,
...@@ -719,13 +718,14 @@ const ErrorHandler = union(enum) {...@@ -719,13 +718,14 @@ const ErrorHandler = union(enum) {
719718
720 try server.serveErrorBundle(error_bundle);719 try server.serveErrorBundle(error_bundle);
721 },720 },
722 .stderr => diagnostics.renderToStdErr(cwd, source, mappings),721 .stderr => return diagnostics.renderToStderr(cwd, source, mappings),
723 }722 }
724 }723 }
725724
726 pub fn emitMessage(725 pub fn emitMessage(
727 self: *ErrorHandler,726 self: *ErrorHandler,
728 allocator: Allocator,727 allocator: Allocator,
728 io: Io,
729 msg_type: @import("utils.zig").ErrorMessageType,729 msg_type: @import("utils.zig").ErrorMessageType,
730 comptime format: []const u8,730 comptime format: []const u8,
731 args: anytype,731 args: anytype,
...@@ -741,9 +741,9 @@ const ErrorHandler = union(enum) {...@@ -741,9 +741,9 @@ const ErrorHandler = union(enum) {
741 try server.serveErrorBundle(error_bundle);741 try server.serveErrorBundle(error_bundle);
742 },742 },
743 .stderr => {743 .stderr => {
744 const stderr, const ttyconf = std.debug.lockStderrWriter(&.{});744 const stderr = try io.lockStderr(&.{}, null);
745 defer std.debug.unlockStderrWriter();745 defer io.unlockStderr();
746 try renderErrorMessage(stderr, ttyconf, msg_type, format, args);746 try renderErrorMessage(stderr.terminal(), msg_type, format, args);
747 },747 },
748 }748 }
749 }749 }
lib/compiler/resinator/utils.zig+22-18
...@@ -1,6 +1,8 @@...@@ -1,6 +1,8 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
32
3const std = @import("std");
4const Io = std.Io;
5
4pub const UncheckedSliceWriter = struct {6pub const UncheckedSliceWriter = struct {
5 const Self = @This();7 const Self = @This();
68
...@@ -23,19 +25,20 @@ pub const UncheckedSliceWriter = struct {...@@ -23,19 +25,20 @@ pub const UncheckedSliceWriter = struct {
23 }25 }
24};26};
2527
26/// Cross-platform 'std.fs.Dir.openFile' wrapper that will always return IsDir if28/// Cross-platform 'Io.Dir.openFile' wrapper that will always return IsDir if
27/// a directory is attempted to be opened.29/// a directory is attempted to be opened.
28/// TODO: Remove once https://github.com/ziglang/zig/issues/5732 is addressed.30/// TODO: Remove once https://github.com/ziglang/zig/issues/5732 is addressed.
29pub fn openFileNotDir(31pub fn openFileNotDir(
30 cwd: std.fs.Dir,32 cwd: Io.Dir,
33 io: Io,
31 path: []const u8,34 path: []const u8,
32 flags: std.fs.File.OpenFlags,35 flags: Io.File.OpenFlags,
33) (std.fs.File.OpenError || std.fs.File.StatError)!std.fs.File {36) (Io.File.OpenError || Io.File.StatError)!Io.File {
34 const file = try cwd.openFile(path, flags);37 const file = try cwd.openFile(io, path, flags);
35 errdefer file.close();38 errdefer file.close(io);
36 // https://github.com/ziglang/zig/issues/573239 // https://github.com/ziglang/zig/issues/5732
37 if (builtin.os.tag != .windows) {40 if (builtin.os.tag != .windows) {
38 const stat = try file.stat();41 const stat = try file.stat(io);
3942
40 if (stat.kind == .directory)43 if (stat.kind == .directory)
41 return error.IsDir;44 return error.IsDir;
...@@ -89,31 +92,32 @@ pub const ErrorMessageType = enum { err, warning, note };...@@ -89,31 +92,32 @@ pub const ErrorMessageType = enum { err, warning, note };
8992
90/// Used for generic colored errors/warnings/notes, more context-specific error messages93/// Used for generic colored errors/warnings/notes, more context-specific error messages
91/// are handled elsewhere.94/// 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;
93 switch (msg_type) {97 switch (msg_type) {
94 .err => {98 .err => {
95 try config.setColor(writer, .bold);99 try t.setColor(.bold);
96 try config.setColor(writer, .red);100 try t.setColor(.red);
97 try writer.writeAll("error: ");101 try writer.writeAll("error: ");
98 },102 },
99 .warning => {103 .warning => {
100 try config.setColor(writer, .bold);104 try t.setColor(.bold);
101 try config.setColor(writer, .yellow);105 try t.setColor(.yellow);
102 try writer.writeAll("warning: ");106 try writer.writeAll("warning: ");
103 },107 },
104 .note => {108 .note => {
105 try config.setColor(writer, .reset);109 try t.setColor(.reset);
106 try config.setColor(writer, .cyan);110 try t.setColor(.cyan);
107 try writer.writeAll("note: ");111 try writer.writeAll("note: ");
108 },112 },
109 }113 }
110 try config.setColor(writer, .reset);114 try t.setColor(.reset);
111 if (msg_type == .err) {115 if (msg_type == .err) {
112 try config.setColor(writer, .bold);116 try t.setColor(.bold);
113 }117 }
114 try writer.print(format, args);118 try writer.print(format, args);
115 try writer.writeByte('\n');119 try writer.writeByte('\n');
116 try config.setColor(writer, .reset);120 try t.setColor(.reset);
117}121}
118122
119pub fn isLineEndingPair(first: u8, second: u8) bool {123pub fn isLineEndingPair(first: u8, second: u8) bool {
lib/compiler/std-docs.zig+70-57
...@@ -1,12 +1,14 @@...@@ -1,12 +1,14 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2
2const std = @import("std");3const std = @import("std");
4const Io = std.Io;
3const mem = std.mem;5const mem = std.mem;
4const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
5const assert = std.debug.assert;7const assert = std.debug.assert;
6const Cache = std.Build.Cache;8const Cache = std.Build.Cache;
79
8fn usage() noreturn {10fn usage(io: Io) noreturn {
9 std.fs.File.stdout().writeAll(11 Io.File.stdout().writeStreamingAll(io,
10 \\Usage: zig std [options]12 \\Usage: zig std [options]
11 \\13 \\
12 \\Options:14 \\Options:
...@@ -27,6 +29,10 @@ pub fn main() !void {...@@ -27,6 +29,10 @@ pub fn main() !void {
27 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;29 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
28 const gpa = general_purpose_allocator.allocator();30 const gpa = general_purpose_allocator.allocator();
2931
32 var threaded: Io.Threaded = .init(gpa, .{});
33 defer threaded.deinit();
34 const io = threaded.io();
35
30 var argv = try std.process.argsWithAllocator(arena);36 var argv = try std.process.argsWithAllocator(arena);
31 defer argv.deinit();37 defer argv.deinit();
32 assert(argv.skip());38 assert(argv.skip());
...@@ -34,18 +40,18 @@ pub fn main() !void {...@@ -34,18 +40,18 @@ pub fn main() !void {
34 const zig_exe_path = argv.next().?;40 const zig_exe_path = argv.next().?;
35 const global_cache_path = argv.next().?;41 const global_cache_path = argv.next().?;
3642
37 var lib_dir = try std.fs.cwd().openDir(zig_lib_directory, .{});43 var lib_dir = try Io.Dir.cwd().openDir(io, zig_lib_directory, .{});
38 defer lib_dir.close();44 defer lib_dir.close(io);
3945
40 var listen_port: u16 = 0;46 var listen_port: u16 = 0;
41 var force_open_browser: ?bool = null;47 var force_open_browser: ?bool = null;
42 while (argv.next()) |arg| {48 while (argv.next()) |arg| {
43 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {49 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
44 usage();50 usage(io);
45 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--port")) {51 } 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| {
47 std.log.err("expected port number: {}", .{err});53 std.log.err("expected port number: {}", .{err});
48 usage();54 usage(io);
49 };55 };
50 } else if (mem.eql(u8, arg, "--open-browser")) {56 } else if (mem.eql(u8, arg, "--open-browser")) {
51 force_open_browser = true;57 force_open_browser = true;
...@@ -53,69 +59,70 @@ pub fn main() !void {...@@ -53,69 +59,70 @@ pub fn main() !void {
53 force_open_browser = false;59 force_open_browser = false;
54 } else {60 } else {
55 std.log.err("unrecognized argument: {s}", .{arg});61 std.log.err("unrecognized argument: {s}", .{arg});
56 usage();62 usage(io);
57 }63 }
58 }64 }
59 const should_open_browser = force_open_browser orelse (listen_port == 0);65 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;67 const address = Io.net.IpAddress.parse("127.0.0.1", listen_port) catch unreachable;
62 var http_server = try address.listen(.{68 var http_server = try address.listen(io, .{
63 .reuse_address = true,69 .reuse_address = true,
64 });70 });
65 const port = http_server.listen_address.in.getPort();71 const port = http_server.socket.address.getPort();
66 const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port});72 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 {};
68 if (should_open_browser) {74 if (should_open_browser) {
69 openBrowserTab(gpa, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| {75 openBrowserTab(gpa, io, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| {
70 std.log.err("unable to open browser: {s}", .{@errorName(err)});76 std.log.err("unable to open browser: {t}", .{err});
71 };77 };
72 }78 }
7379
74 var context: Context = .{80 var context: Context = .{
75 .gpa = gpa,81 .gpa = gpa,
82 .io = io,
76 .zig_exe_path = zig_exe_path,83 .zig_exe_path = zig_exe_path,
77 .global_cache_path = global_cache_path,84 .global_cache_path = global_cache_path,
78 .lib_dir = lib_dir,85 .lib_dir = lib_dir,
79 .zig_lib_directory = zig_lib_directory,86 .zig_lib_directory = zig_lib_directory,
80 };87 };
8188
89 var group: Io.Group = .init;
90 defer group.cancel(io);
91
82 while (true) {92 while (true) {
83 const connection = try http_server.accept();93 const stream = try http_server.accept(io);
84 _ = std.Thread.spawn(.{}, accept, .{ &context, connection }) catch |err| {94 group.async(io, accept, .{ &context, stream });
85 std.log.err("unable to accept connection: {s}", .{@errorName(err)});
86 connection.stream.close();
87 continue;
88 };
89 }95 }
90}96}
9197
92fn accept(context: *Context, connection: std.net.Server.Connection) void {98fn accept(context: *Context, stream: Io.net.Stream) void {
93 defer connection.stream.close();99 const io = context.io;
100 defer stream.close(io);
94101
95 var recv_buffer: [4000]u8 = undefined;102 var recv_buffer: [4000]u8 = undefined;
96 var send_buffer: [4000]u8 = undefined;103 var send_buffer: [4000]u8 = undefined;
97 var conn_reader = connection.stream.reader(&recv_buffer);104 var conn_reader = stream.reader(io, &recv_buffer);
98 var conn_writer = connection.stream.writer(&send_buffer);105 var conn_writer = stream.writer(io, &send_buffer);
99 var server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface);106 var server = std.http.Server.init(&conn_reader.interface, &conn_writer.interface);
100 while (server.reader.state == .ready) {107 while (server.reader.state == .ready) {
101 var request = server.receiveHead() catch |err| switch (err) {108 var request = server.receiveHead() catch |err| switch (err) {
102 error.HttpConnectionClosing => return,109 error.HttpConnectionClosing => return,
103 else => {110 else => {
104 std.log.err("closing http connection: {s}", .{@errorName(err)});111 std.log.err("closing http connection: {t}", .{err});
105 return;112 return;
106 },113 },
107 };114 };
108 serveRequest(&request, context) catch |err| switch (err) {115 serveRequest(&request, context) catch |err| switch (err) {
109 error.WriteFailed => {116 error.WriteFailed => {
110 if (conn_writer.err) |e| {117 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 });
112 } else {119 } 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 });
114 }121 }
115 return;122 return;
116 },123 },
117 else => {124 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 });
119 return;126 return;
120 },127 },
121 };128 };
...@@ -124,7 +131,8 @@ fn accept(context: *Context, connection: std.net.Server.Connection) void {...@@ -124,7 +131,8 @@ fn accept(context: *Context, connection: std.net.Server.Connection) void {
124131
125const Context = struct {132const Context = struct {
126 gpa: Allocator,133 gpa: Allocator,
127 lib_dir: std.fs.Dir,134 io: Io,
135 lib_dir: Io.Dir,
128 zig_lib_directory: []const u8,136 zig_lib_directory: []const u8,
129 zig_exe_path: []const u8,137 zig_exe_path: []const u8,
130 global_cache_path: []const u8,138 global_cache_path: []const u8,
...@@ -170,10 +178,11 @@ fn serveDocsFile(...@@ -170,10 +178,11 @@ fn serveDocsFile(
170 content_type: []const u8,178 content_type: []const u8,
171) !void {179) !void {
172 const gpa = context.gpa;180 const gpa = context.gpa;
181 const io = context.io;
173 // The desired API is actually sendfile, which will require enhancing std.http.Server.182 // The desired API is actually sendfile, which will require enhancing std.http.Server.
174 // We load the file with every request so that the user can make changes to the file183 // We load the file with every request so that the user can make changes to the file
175 // and refresh the HTML page without restarting this server.184 // 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));
177 defer gpa.free(file_contents);186 defer gpa.free(file_contents);
178 try request.respond(file_contents, .{187 try request.respond(file_contents, .{
179 .extra_headers = &.{188 .extra_headers = &.{
...@@ -185,6 +194,7 @@ fn serveDocsFile(...@@ -185,6 +194,7 @@ fn serveDocsFile(
185194
186fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {195fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
187 const gpa = context.gpa;196 const gpa = context.gpa;
197 const io = context.io;
188198
189 var send_buffer: [0x4000]u8 = undefined;199 var send_buffer: [0x4000]u8 = undefined;
190 var response = try request.respondStreaming(&send_buffer, .{200 var response = try request.respondStreaming(&send_buffer, .{
...@@ -196,8 +206,8 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {...@@ -196,8 +206,8 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
196 },206 },
197 });207 });
198208
199 var std_dir = try context.lib_dir.openDir("std", .{ .iterate = true });209 var std_dir = try context.lib_dir.openDir(io, "std", .{ .iterate = true });
200 defer std_dir.close();210 defer std_dir.close(io);
201211
202 var walker = try std_dir.walk(gpa);212 var walker = try std_dir.walk(gpa);
203 defer walker.deinit();213 defer walker.deinit();
...@@ -205,7 +215,7 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {...@@ -205,7 +215,7 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
205 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };215 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
206 archiver.prefix = "std";216 archiver.prefix = "std";
207217
208 while (try walker.next()) |entry| {218 while (try walker.next(io)) |entry| {
209 switch (entry.kind) {219 switch (entry.kind) {
210 .file => {220 .file => {
211 if (!std.mem.endsWith(u8, entry.basename, ".zig"))221 if (!std.mem.endsWith(u8, entry.basename, ".zig"))
...@@ -215,15 +225,16 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {...@@ -215,15 +225,16 @@ fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
215 },225 },
216 else => continue,226 else => continue,
217 }227 }
218 var file = try entry.dir.openFile(entry.basename, .{});228 var file = try entry.dir.openFile(io, entry.basename, .{});
219 defer file.close();229 defer file.close(io);
220 const stat = try file.stat();230 const stat = try file.stat(io);
221 var file_reader: std.fs.File.Reader = .{231 var file_reader: Io.File.Reader = .{
232 .io = io,
222 .file = file,233 .file = file,
223 .interface = std.fs.File.Reader.initInterface(&.{}),234 .interface = Io.File.Reader.initInterface(&.{}),
224 .size = stat.size,235 .size = stat.size,
225 };236 };
226 try archiver.writeFile(entry.path, &file_reader, stat.mtime);237 try archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime);
227 }238 }
228239
229 {240 {
...@@ -245,6 +256,7 @@ fn serveWasm(...@@ -245,6 +256,7 @@ fn serveWasm(
245 optimize_mode: std.builtin.OptimizeMode,256 optimize_mode: std.builtin.OptimizeMode,
246) !void {257) !void {
247 const gpa = context.gpa;258 const gpa = context.gpa;
259 const io = context.io;
248260
249 var arena_instance = std.heap.ArenaAllocator.init(gpa);261 var arena_instance = std.heap.ArenaAllocator.init(gpa);
250 defer arena_instance.deinit();262 defer arena_instance.deinit();
...@@ -255,7 +267,7 @@ fn serveWasm(...@@ -255,7 +267,7 @@ fn serveWasm(
255 const wasm_base_path = try buildWasmBinary(arena, context, optimize_mode);267 const wasm_base_path = try buildWasmBinary(arena, context, optimize_mode);
256 const bin_name = try std.zig.binNameAlloc(arena, .{268 const bin_name = try std.zig.binNameAlloc(arena, .{
257 .root_name = autodoc_root_name,269 .root_name = autodoc_root_name,
258 .target = &(std.zig.system.resolveTargetQuery(std.Build.parseTargetQuery(.{270 .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
259 .arch_os_abi = autodoc_arch_os_abi,271 .arch_os_abi = autodoc_arch_os_abi,
260 .cpu_features = autodoc_cpu_features,272 .cpu_features = autodoc_cpu_features,
261 }) catch unreachable) catch unreachable),273 }) catch unreachable) catch unreachable),
...@@ -263,7 +275,7 @@ fn serveWasm(...@@ -263,7 +275,7 @@ fn serveWasm(
263 });275 });
264 // std.http.Server does not have a sendfile API yet.276 // std.http.Server does not have a sendfile API yet.
265 const bin_path = try wasm_base_path.join(arena, bin_name);277 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));
267 defer gpa.free(file_contents);279 defer gpa.free(file_contents);
268 try request.respond(file_contents, .{280 try request.respond(file_contents, .{
269 .extra_headers = &.{281 .extra_headers = &.{
...@@ -283,6 +295,7 @@ fn buildWasmBinary(...@@ -283,6 +295,7 @@ fn buildWasmBinary(
283 optimize_mode: std.builtin.OptimizeMode,295 optimize_mode: std.builtin.OptimizeMode,
284) !Cache.Path {296) !Cache.Path {
285 const gpa = context.gpa;297 const gpa = context.gpa;
298 const io = context.io;
286299
287 var argv: std.ArrayList([]const u8) = .empty;300 var argv: std.ArrayList([]const u8) = .empty;
288301
...@@ -315,16 +328,16 @@ fn buildWasmBinary(...@@ -315,16 +328,16 @@ fn buildWasmBinary(
315 child.stdin_behavior = .Pipe;328 child.stdin_behavior = .Pipe;
316 child.stdout_behavior = .Pipe;329 child.stdout_behavior = .Pipe;
317 child.stderr_behavior = .Pipe;330 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 }, .{
321 .stdout = child.stdout.?,334 .stdout = child.stdout.?,
322 .stderr = child.stderr.?,335 .stderr = child.stderr.?,
323 });336 });
324 defer poller.deinit();337 defer poller.deinit();
325338
326 try sendMessage(child.stdin.?, .update);339 try sendMessage(io, child.stdin.?, .update);
327 try sendMessage(child.stdin.?, .exit);340 try sendMessage(io, child.stdin.?, .exit);
328341
329 var result: ?Cache.Path = null;342 var result: ?Cache.Path = null;
330 var result_error_bundle = std.zig.ErrorBundle.empty;343 var result_error_bundle = std.zig.ErrorBundle.empty;
...@@ -348,7 +361,7 @@ fn buildWasmBinary(...@@ -348,7 +361,7 @@ fn buildWasmBinary(
348 result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);361 result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
349 },362 },
350 .emit_digest => {363 .emit_digest => {
351 var r: std.Io.Reader = .fixed(body);364 var r: Io.Reader = .fixed(body);
352 const emit_digest = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;365 const emit_digest = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
353 if (!emit_digest.flags.cache_hit) {366 if (!emit_digest.flags.cache_hit) {
354 std.log.info("source changes detected; rebuilt wasm component", .{});367 std.log.info("source changes detected; rebuilt wasm component", .{});
...@@ -371,10 +384,10 @@ fn buildWasmBinary(...@@ -371,10 +384,10 @@ fn buildWasmBinary(
371 }384 }
372385
373 // Send EOF to stdin.386 // Send EOF to stdin.
374 child.stdin.?.close();387 child.stdin.?.close(io);
375 child.stdin = null;388 child.stdin = null;
376389
377 switch (try child.wait()) {390 switch (try child.wait(io)) {
378 .Exited => |code| {391 .Exited => |code| {
379 if (code != 0) {392 if (code != 0) {
380 std.log.err(393 std.log.err(
...@@ -394,7 +407,7 @@ fn buildWasmBinary(...@@ -394,7 +407,7 @@ fn buildWasmBinary(
394 }407 }
395408
396 if (result_error_bundle.errorMessageCount() > 0) {409 if (result_error_bundle.errorMessageCount() > 0) {
397 result_error_bundle.renderToStdErr(.{}, true);410 try result_error_bundle.renderToStderr(io, .{}, .auto);
398 std.log.err("the following command failed with {d} compilation errors:\n{s}", .{411 std.log.err("the following command failed with {d} compilation errors:\n{s}", .{
399 result_error_bundle.errorMessageCount(),412 result_error_bundle.errorMessageCount(),
400 try std.Build.Step.allocPrintCmd(arena, null, argv.items),413 try std.Build.Step.allocPrintCmd(arena, null, argv.items),
...@@ -410,24 +423,24 @@ fn buildWasmBinary(...@@ -410,24 +423,24 @@ fn buildWasmBinary(
410 };423 };
411}424}
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 {
414 const header: std.zig.Client.Message.Header = .{427 const header: std.zig.Client.Message.Header = .{
415 .tag = tag,428 .tag = tag,
416 .bytes_len = 0,429 .bytes_len = 0,
417 };430 };
418 var w = file.writer(&.{});431 var w = file.writer(io, &.{});
419 w.interface.writeStruct(header, .little) catch |err| switch (err) {432 w.interface.writeStruct(header, .little) catch |err| switch (err) {
420 error.WriteFailed => return w.err.?,433 error.WriteFailed => return w.err.?,
421 };434 };
422}435}
423436
424fn openBrowserTab(gpa: Allocator, url: []const u8) !void {437fn openBrowserTab(gpa: Allocator, io: Io, url: []const u8) !void {
425 // Until https://github.com/ziglang/zig/issues/19205 is implemented, we438 // Until https://github.com/ziglang/zig/issues/19205 is implemented, we
426 // spawn a thread for this child process.439 // spawn a thread for this child process.
427 _ = try std.Thread.spawn(.{}, openBrowserTabThread, .{ gpa, url });440 _ = try std.Thread.spawn(.{}, openBrowserTabThread, .{ gpa, io, url });
428}441}
429442
430fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void {443fn openBrowserTabThread(gpa: Allocator, io: Io, url: []const u8) !void {
431 const main_exe = switch (builtin.os.tag) {444 const main_exe = switch (builtin.os.tag) {
432 .windows => "explorer",445 .windows => "explorer",
433 .macos => "open",446 .macos => "open",
...@@ -437,6 +450,6 @@ fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void {...@@ -437,6 +450,6 @@ fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void {
437 child.stdin_behavior = .Ignore;450 child.stdin_behavior = .Ignore;
438 child.stdout_behavior = .Ignore;451 child.stdout_behavior = .Ignore;
439 child.stderr_behavior = .Ignore;452 child.stderr_behavior = .Ignore;
440 try child.spawn();453 try child.spawn(io);
441 _ = try child.wait();454 _ = try child.wait(io);
442}455}
lib/compiler/test_runner.zig+26-20
...@@ -17,7 +17,7 @@ var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);...@@ -17,7 +17,7 @@ var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);
17var fba_buffer: [8192]u8 = undefined;17var fba_buffer: [8192]u8 = undefined;
18var stdin_buffer: [4096]u8 = undefined;18var stdin_buffer: [4096]u8 = undefined;
19var stdout_buffer: [4096]u8 = undefined;19var 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
22/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether22/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether
23/// the test runner will communicate with the build runner via `std.zig.Server`.23/// the test runner will communicate with the build runner via `std.zig.Server`.
...@@ -74,8 +74,8 @@ pub fn main() void {...@@ -74,8 +74,8 @@ pub fn main() void {
7474
75fn mainServer() !void {75fn mainServer() !void {
76 @disableInstrumentation();76 @disableInstrumentation();
77 var stdin_reader = std.fs.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer);77 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);
78 var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);78 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);
79 var server = try std.zig.Server.init(.{79 var server = try std.zig.Server.init(.{
80 .in = &stdin_reader.interface,80 .in = &stdin_reader.interface,
81 .out = &stdout_writer.interface,81 .out = &stdout_writer.interface,
...@@ -131,7 +131,7 @@ fn mainServer() !void {...@@ -131,7 +131,7 @@ fn mainServer() !void {
131131
132 .run_test => {132 .run_test => {
133 testing.allocator_instance = .{};133 testing.allocator_instance = .{};
134 testing.io_instance = .init(testing.allocator);134 testing.io_instance = .init(testing.allocator, .{});
135 log_err_count = 0;135 log_err_count = 0;
136 const index = try server.receiveBody_u32();136 const index = try server.receiveBody_u32();
137 const test_fn = builtin.test_functions[index];137 const test_fn = builtin.test_functions[index];
...@@ -224,16 +224,16 @@ fn mainTerminal() void {...@@ -224,16 +224,16 @@ fn mainTerminal() void {
224 var skip_count: usize = 0;224 var skip_count: usize = 0;
225 var fail_count: usize = 0;225 var fail_count: usize = 0;
226 var fuzz_count: usize = 0;226 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, .{
228 .root_name = "Test",228 .root_name = "Test",
229 .estimated_total_items = test_fn_list.len,229 .estimated_total_items = test_fn_list.len,
230 });230 });
231 const have_tty = std.fs.File.stderr().isTty();231 const have_tty = Io.File.stderr().isTty(runner_threaded_io) catch unreachable;
232232
233 var leaks: usize = 0;233 var leaks: usize = 0;
234 for (test_fn_list, 0..) |test_fn, i| {234 for (test_fn_list, 0..) |test_fn, i| {
235 testing.allocator_instance = .{};235 testing.allocator_instance = .{};
236 testing.io_instance = .init(testing.allocator);236 testing.io_instance = .init(testing.allocator, .{});
237 defer {237 defer {
238 testing.io_instance.deinit();238 testing.io_instance.deinit();
239 if (testing.allocator_instance.deinit() == .leak) leaks += 1;239 if (testing.allocator_instance.deinit() == .leak) leaks += 1;
...@@ -318,7 +318,7 @@ pub fn log(...@@ -318,7 +318,7 @@ pub fn log(
318/// work-in-progress backends can handle it.318/// work-in-progress backends can handle it.
319pub fn mainSimple() anyerror!void {319pub fn mainSimple() anyerror!void {
320 @disableInstrumentation();320 @disableInstrumentation();
321 // is the backend capable of calling `std.fs.File.writeAll`?321 // is the backend capable of calling `Io.File.writeAll`?
322 const enable_write = switch (builtin.zig_backend) {322 const enable_write = switch (builtin.zig_backend) {
323 .stage2_aarch64, .stage2_riscv64 => true,323 .stage2_aarch64, .stage2_riscv64 => true,
324 else => false,324 else => false,
...@@ -329,35 +329,37 @@ pub fn mainSimple() anyerror!void {...@@ -329,35 +329,37 @@ pub fn mainSimple() anyerror!void {
329 else => false,329 else => false,
330 };330 };
331331
332 testing.io_instance = .init(testing.allocator, .{});
333
332 var passed: u64 = 0;334 var passed: u64 = 0;
333 var skipped: u64 = 0;335 var skipped: u64 = 0;
334 var failed: u64 = 0;336 var failed: u64 = 0;
335337
336 // we don't want to bring in File and Writer if the backend doesn't support it338 // 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
339 for (builtin.test_functions) |test_fn| {341 for (builtin.test_functions) |test_fn| {
340 if (enable_write) {342 if (enable_write) {
341 stdout.writeAll(test_fn.name) catch {};343 stdout.writeStreamingAll(runner_threaded_io, test_fn.name) catch {};
342 stdout.writeAll("... ") catch {};344 stdout.writeStreamingAll(runner_threaded_io, "... ") catch {};
343 }345 }
344 if (test_fn.func()) |_| {346 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 {};
346 } else |err| {348 } else |err| {
347 if (err != error.SkipZigTest) {349 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 {};
349 failed += 1;351 failed += 1;
350 if (!enable_write) return err;352 if (!enable_write) return err;
351 continue;353 continue;
352 }354 }
353 if (enable_write) stdout.writeAll("SKIP\n") catch {};355 if (enable_write) stdout.writeStreamingAll(runner_threaded_io, "SKIP\n") catch {};
354 skipped += 1;356 skipped += 1;
355 continue;357 continue;
356 }358 }
357 passed += 1;359 passed += 1;
358 }360 }
359 if (enable_print) {361 if (enable_print) {
360 var stdout_writer = stdout.writer(&.{});362 var stdout_writer = stdout.writer(runner_threaded_io, &.{});
361 stdout_writer.interface.print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {};363 stdout_writer.interface.print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {};
362 }364 }
363 if (failed != 0) std.process.exit(1);365 if (failed != 0) std.process.exit(1);
...@@ -405,15 +407,19 @@ pub fn fuzz(...@@ -405,15 +407,19 @@ pub fn fuzz(
405 testOne(ctx, input.toSlice()) catch |err| switch (err) {407 testOne(ctx, input.toSlice()) catch |err| switch (err) {
406 error.SkipZigTest => return,408 error.SkipZigTest => return,
407 else => {409 else => {
408 std.debug.lockStdErr();410 const stderr = std.debug.lockStderr(&.{}, null).terminal();
409 if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace);411 p: {
410 std.debug.print("failed with error.{t}\n", .{err});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 }
411 std.process.exit(1);417 std.process.exit(1);
412 },418 },
413 };419 };
414 if (log_err_count != 0) {420 if (log_err_count != 0) {
415 std.debug.lockStdErr();421 const stderr = std.debug.lockStderr(&.{}, .no_color);
416 std.debug.print("error logs detected\n", .{});422 stderr.interface.print("error logs detected\n", .{}) catch {};
417 std.process.exit(1);423 std.process.exit(1);
418 }424 }
419 }425 }
lib/compiler/translate-c/main.zig+23-18
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const assert = std.debug.assert;3const assert = std.debug.assert;
3const mem = std.mem;4const mem = std.mem;
4const process = std.process;5const process = std.process;
...@@ -18,7 +19,7 @@ pub fn main() u8 {...@@ -18,7 +19,7 @@ pub fn main() u8 {
18 defer arena_instance.deinit();19 defer arena_instance.deinit();
19 const arena = arena_instance.allocator();20 const arena = arena_instance.allocator();
2021
21 var threaded: std.Io.Threaded = .init(gpa);22 var threaded: std.Io.Threaded = .init(gpa, .{});
22 defer threaded.deinit();23 defer threaded.deinit();
23 const io = threaded.io();24 const io = threaded.io();
2425
...@@ -33,11 +34,14 @@ pub fn main() u8 {...@@ -33,11 +34,14 @@ pub fn main() u8 {
33 zig_integration = true;34 zig_integration = true;
34 }35 }
3536
37 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet();
38 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet();
39
36 var stderr_buf: [1024]u8 = undefined;40 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);
38 var diagnostics: aro.Diagnostics = switch (zig_integration) {42 var diagnostics: aro.Diagnostics = switch (zig_integration) {
39 false => .{ .output = .{ .to_writer = .{43 false => .{ .output = .{ .to_writer = .{
40 .color = .detect(stderr.file),44 .mode = Io.Terminal.Mode.detect(io, stderr.file, NO_COLOR, CLICOLOR_FORCE) catch unreachable,
41 .writer = &stderr.interface,45 .writer = &stderr.interface,
42 } } },46 } } },
43 true => .{ .output = .{ .to_list = .{47 true => .{ .output = .{ .to_list = .{
...@@ -46,7 +50,7 @@ pub fn main() u8 {...@@ -46,7 +50,7 @@ pub fn main() u8 {
46 };50 };
47 defer diagnostics.deinit();51 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) {
50 error.OutOfMemory => {54 error.OutOfMemory => {
51 std.debug.print("ran out of memory initializing C compilation\n", .{});55 std.debug.print("ran out of memory initializing C compilation\n", .{});
52 if (fast_exit) process.exit(1);56 if (fast_exit) process.exit(1);
...@@ -68,7 +72,7 @@ pub fn main() u8 {...@@ -68,7 +72,7 @@ pub fn main() u8 {
68 return 1;72 return 1;
69 },73 },
70 error.FatalError => if (zig_integration) {74 error.FatalError => if (zig_integration) {
71 serveErrorBundle(arena, &diagnostics) catch |bundle_err| {75 serveErrorBundle(arena, io, &diagnostics) catch |bundle_err| {
72 std.debug.print("unable to serve error bundle: {}\n", .{bundle_err});76 std.debug.print("unable to serve error bundle: {}\n", .{bundle_err});
73 if (fast_exit) process.exit(1);77 if (fast_exit) process.exit(1);
74 return 1;78 return 1;
...@@ -92,14 +96,14 @@ pub fn main() u8 {...@@ -92,14 +96,14 @@ pub fn main() u8 {
92 return @intFromBool(comp.diagnostics.errors != 0);96 return @intFromBool(comp.diagnostics.errors != 0);
93}97}
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 {
96 const error_bundle = try compiler_util.aroDiagnosticsToErrorBundle(100 const error_bundle = try compiler_util.aroDiagnosticsToErrorBundle(
97 diagnostics,101 diagnostics,
98 arena,102 arena,
99 "translation failure",103 "translation failure",
100 );104 );
101 var stdout_buffer: [1024]u8 = undefined;105 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);
103 var server: std.zig.Server = .{107 var server: std.zig.Server = .{
104 .out = &stdout_writer.interface,108 .out = &stdout_writer.interface,
105 .in = undefined,109 .in = undefined,
...@@ -121,6 +125,7 @@ pub const usage =...@@ -121,6 +125,7 @@ pub const usage =
121125
122fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration: bool) !void {126fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration: bool) !void {
123 const gpa = d.comp.gpa;127 const gpa = d.comp.gpa;
128 const io = d.comp.io;
124129
125 const aro_args = args: {130 const aro_args = args: {
126 var i: usize = 0;131 var i: usize = 0;
...@@ -128,13 +133,13 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration...@@ -128,13 +133,13 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration
128 args[i] = arg;133 args[i] = arg;
129 if (mem.eql(u8, arg, "--help")) {134 if (mem.eql(u8, arg, "--help")) {
130 var stdout_buf: [512]u8 = undefined;135 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);
132 try stdout.interface.print(usage, .{args[0]});137 try stdout.interface.print(usage, .{args[0]});
133 try stdout.interface.flush();138 try stdout.interface.flush();
134 return;139 return;
135 } else if (mem.eql(u8, arg, "--version")) {140 } else if (mem.eql(u8, arg, "--version")) {
136 var stdout_buf: [512]u8 = undefined;141 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);
138 // TODO add version143 // TODO add version
139 try stdout.interface.writeAll("0.0.0-dev\n");144 try stdout.interface.writeAll("0.0.0-dev\n");
140 try stdout.interface.flush();145 try stdout.interface.flush();
...@@ -224,13 +229,13 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration...@@ -224,13 +229,13 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration
224 const dep_file_name = try d.getDepFileName(source, out_buf[0..std.fs.max_name_bytes]);229 const dep_file_name = try d.getDepFileName(source, out_buf[0..std.fs.max_name_bytes]);
225230
226 const file = if (dep_file_name) |path|231 const file = if (dep_file_name) |path|
227 d.comp.cwd.createFile(path, .{}) catch |er|232 d.comp.cwd.createFile(io, path, .{}) catch |er|
228 return d.fatal("unable to create dependency file '{s}': {s}", .{ path, aro.Driver.errorDescription(er) })233 return d.fatal("unable to create dependency file '{s}': {s}", .{ path, aro.Driver.errorDescription(er) })
229 else234 else
230 std.fs.File.stdout();235 Io.File.stdout();
231 defer if (dep_file_name != null) file.close();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);
234 dep_file.write(&file_writer.interface) catch239 dep_file.write(&file_writer.interface) catch
235 return d.fatal("unable to write dependency file: {s}", .{aro.Driver.errorDescription(file_writer.err.?)});240 return d.fatal("unable to write dependency file: {s}", .{aro.Driver.errorDescription(file_writer.err.?)});
236 }241 }
...@@ -245,23 +250,23 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration...@@ -245,23 +250,23 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration
245250
246 var close_out_file = false;251 var close_out_file = false;
247 var out_file_path: []const u8 = "<stdout>";252 var out_file_path: []const u8 = "<stdout>";
248 var out_file: std.fs.File = .stdout();253 var out_file: Io.File = .stdout();
249 defer if (close_out_file) out_file.close();254 defer if (close_out_file) out_file.close(io);
250255
251 if (d.output_name) |path| blk: {256 if (d.output_name) |path| blk: {
252 if (std.mem.eql(u8, path, "-")) break :blk;257 if (std.mem.eql(u8, path, "-")) break :blk;
253 if (std.fs.path.dirname(path)) |dirname| {258 if (std.fs.path.dirname(path)) |dirname| {
254 std.fs.cwd().makePath(dirname) catch |err|259 Io.Dir.cwd().createDirPath(io, dirname) catch |err|
255 return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) });260 return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) });
256 }261 }
257 out_file = std.fs.cwd().createFile(path, .{}) catch |err| {262 out_file = Io.Dir.cwd().createFile(io, path, .{}) catch |err| {
258 return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) });263 return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) });
259 };264 };
260 close_out_file = true;265 close_out_file = true;
261 out_file_path = path;266 out_file_path = path;
262 }267 }
263268
264 var out_writer = out_file.writer(&out_buf);269 var out_writer = out_file.writer(io, &out_buf);
265 out_writer.interface.writeAll(rendered_zig) catch {};270 out_writer.interface.writeAll(rendered_zig) catch {};
266 out_writer.interface.flush() catch {};271 out_writer.interface.flush() catch {};
267 if (out_writer.err) |write_err|272 if (out_writer.err) |write_err|
lib/compiler_rt/emutls.zig+1-1
...@@ -7,7 +7,7 @@ const std = @import("std");...@@ -7,7 +7,7 @@ const std = @import("std");
7const builtin = @import("builtin");7const builtin = @import("builtin");
8const common = @import("common.zig");8const common = @import("common.zig");
99
10const abort = std.posix.abort;10const abort = std.process.abort;
11const assert = std.debug.assert;11const assert = std.debug.assert;
12const expect = std.testing.expect;12const expect = std.testing.expect;
1313
lib/fuzzer.zig+39-45
...@@ -1,18 +1,22 @@...@@ -1,18 +1,22 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
2const std = @import("std");4const std = @import("std");
5const Io = std.Io;
3const fatal = std.process.fatal;6const fatal = std.process.fatal;
4const mem = std.mem;7const mem = std.mem;
5const math = std.math;8const math = std.math;
6const Allocator = mem.Allocator;9const Allocator = std.mem.Allocator;
7const assert = std.debug.assert;10const assert = std.debug.assert;
8const panic = std.debug.panic;11const panic = std.debug.panic;
9const abi = std.Build.abi.fuzz;12const abi = std.Build.abi.fuzz;
10const native_endian = builtin.cpu.arch.endian();
1113
12pub const std_options = std.Options{14pub const std_options = std.Options{
13 .logFn = logOverride,15 .logFn = logOverride,
14};16};
1517
18const io = std.Io.Threaded.global_single_threaded.ioBasic();
19
16fn logOverride(20fn logOverride(
17 comptime level: std.log.Level,21 comptime level: std.log.Level,
18 comptime scope: @EnumLiteral(),22 comptime scope: @EnumLiteral(),
...@@ -21,12 +25,12 @@ fn logOverride(...@@ -21,12 +25,12 @@ fn logOverride(
21) void {25) void {
22 const f = log_f orelse26 const f = log_f orelse
23 panic("attempt to use log before initialization, message:\n" ++ format, args);27 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});28 f.lock(io, .exclusive) catch |e| panic("failed to lock logging file: {t}", .{e});
25 defer f.unlock();29 defer f.unlock(io);
2630
27 var buf: [256]u8 = undefined;31 var buf: [256]u8 = undefined;
28 var fw = f.writer(&buf);32 var fw = f.writer(io, &buf);
29 const end = f.getEndPos() catch |e| panic("failed to get fuzzer log file end: {t}", .{e});33 const end = f.length(io) catch |e| panic("failed to get fuzzer log file end: {t}", .{e});
30 fw.seekTo(end) catch |e| panic("failed to seek to fuzzer log file end: {t}", .{e});34 fw.seekTo(end) catch |e| panic("failed to seek to fuzzer log file end: {t}", .{e});
3135
32 const prefix1 = comptime level.asText();36 const prefix1 = comptime level.asText();
...@@ -45,7 +49,7 @@ const gpa = switch (builtin.mode) {...@@ -45,7 +49,7 @@ const gpa = switch (builtin.mode) {
45};49};
4650
47/// Part of `exec`, however seperate to allow it to be set before `exec` is.51/// 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;
49var exec: Executable = .preinit;53var exec: Executable = .preinit;
50var inst: Instrumentation = .preinit;54var inst: Instrumentation = .preinit;
51var fuzzer: Fuzzer = undefined;55var fuzzer: Fuzzer = undefined;
...@@ -59,7 +63,7 @@ const Executable = struct {...@@ -59,7 +63,7 @@ const Executable = struct {
59 /// Tracks the hit count for each pc as updated by the process's instrumentation.63 /// Tracks the hit count for each pc as updated by the process's instrumentation.
60 pc_counters: []u8,64 pc_counters: []u8,
6165
62 cache_f: std.fs.Dir,66 cache_f: Io.Dir,
63 /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed67 /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed
64 /// while the fuzzer is running.68 /// while the fuzzer is running.
65 shared_seen_pcs: MemoryMappedList,69 shared_seen_pcs: MemoryMappedList,
...@@ -76,16 +80,16 @@ const Executable = struct {...@@ -76,16 +80,16 @@ const Executable = struct {
76 .pc_digest = undefined,80 .pc_digest = undefined,
77 };81 };
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 {
80 const pc_bitset_usizes = bitsetUsizes(pcs.len);84 const pc_bitset_usizes = bitsetUsizes(pcs.len);
81 const coverage_file_name = std.fmt.hex(pc_digest);85 const coverage_file_name = std.fmt.hex(pc_digest);
82 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);86 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
83 comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);87 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|
86 panic("failed to create directory 'v': {t}", .{e});90 panic("failed to create directory 'v': {t}", .{e});
87 defer v.close();91 defer v.close(io);
88 const coverage_file, const populate = if (v.createFile(&coverage_file_name, .{92 const coverage_file, const populate = if (v.createFile(io, &coverage_file_name, .{
89 .read = true,93 .read = true,
90 // If we create the file, we want to block other processes while we populate it94 // If we create the file, we want to block other processes while we populate it
91 .lock = .exclusive,95 .lock = .exclusive,
...@@ -93,7 +97,7 @@ const Executable = struct {...@@ -93,7 +97,7 @@ const Executable = struct {
93 })) |f|97 })) |f|
94 .{ f, true }98 .{ f, true }
95 else |e| switch (e) {99 else |e| switch (e) {
96 error.PathAlreadyExists => .{ v.openFile(&coverage_file_name, .{100 error.PathAlreadyExists => .{ v.openFile(io, &coverage_file_name, .{
97 .mode = .read_write,101 .mode = .read_write,
98 .lock = .shared,102 .lock = .shared,
99 }) catch |e2| panic(103 }) catch |e2| panic(
...@@ -108,7 +112,7 @@ const Executable = struct {...@@ -108,7 +112,7 @@ const Executable = struct {
108 pcs.len * @sizeOf(usize);112 pcs.len * @sizeOf(usize);
109113
110 if (populate) {114 if (populate) {
111 defer coverage_file.lock(.shared) catch |e| panic(115 defer coverage_file.lock(io, .shared) catch |e| panic(
112 "failed to demote lock for coverage file '{s}': {t}",116 "failed to demote lock for coverage file '{s}': {t}",
113 .{ &coverage_file_name, e },117 .{ &coverage_file_name, e },
114 );118 );
...@@ -130,10 +134,8 @@ const Executable = struct {...@@ -130,10 +134,8 @@ const Executable = struct {
130 }134 }
131 return map;135 return map;
132 } else {136 } else {
133 const size = coverage_file.getEndPos() catch |e| panic(137 const size = coverage_file.length(io) catch |e|
134 "failed to stat coverage file '{s}': {t}",138 panic("failed to stat coverage file '{s}': {t}", .{ &coverage_file_name, e });
135 .{ &coverage_file_name, e },
136 );
137 if (size != coverage_file_len) panic(139 if (size != coverage_file_len) panic(
138 "incompatible existing coverage file '{s}' (differing lengths: {} != {})",140 "incompatible existing coverage file '{s}' (differing lengths: {} != {})",
139 .{ &coverage_file_name, size, coverage_file_len },141 .{ &coverage_file_name, size, coverage_file_len },
...@@ -165,13 +167,11 @@ const Executable = struct {...@@ -165,13 +167,11 @@ const Executable = struct {
165 pub fn init(cache_dir_path: []const u8) Executable {167 pub fn init(cache_dir_path: []const u8) Executable {
166 var self: Executable = undefined;168 var self: Executable = undefined;
167169
168 const cache_dir = std.fs.cwd().makeOpenPath(cache_dir_path, .{}) catch |e| panic(170 const cache_dir = Io.Dir.cwd().createDirPathOpen(io, cache_dir_path, .{}) catch |e|
169 "failed to open directory '{s}': {t}",171 panic("failed to open directory '{s}': {t}", .{ cache_dir_path, e });
170 .{ cache_dir_path, e },172 log_f = cache_dir.createFile(io, "tmp/libfuzzer.log", .{ .truncate = false }) catch |e|
171 );
172 log_f = cache_dir.createFile("tmp/libfuzzer.log", .{ .truncate = false }) catch |e|
173 panic("failed to create file 'tmp/libfuzzer.log': {t}", .{e});173 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|
175 panic("failed to open directory 'f': {t}", .{e});175 panic("failed to open directory 'f': {t}", .{e});
176176
177 // Linkers are expected to automatically add symbols prefixed with these for the start and177 // Linkers are expected to automatically add symbols prefixed with these for the start and
...@@ -391,7 +391,7 @@ const Fuzzer = struct {...@@ -391,7 +391,7 @@ const Fuzzer = struct {
391 mutations: std.ArrayList(Mutation) = .empty,391 mutations: std.ArrayList(Mutation) = .empty,
392392
393 /// Filesystem directory containing found inputs for future runs393 /// Filesystem directory containing found inputs for future runs
394 corpus_dir: std.fs.Dir,394 corpus_dir: Io.Dir,
395 corpus_dir_idx: usize = 0,395 corpus_dir_idx: usize = 0,
396396
397 pub fn init(test_one: abi.TestOne, unit_test_name: []const u8) Fuzzer {397 pub fn init(test_one: abi.TestOne, unit_test_name: []const u8) Fuzzer {
...@@ -405,10 +405,10 @@ const Fuzzer = struct {...@@ -405,10 +405,10 @@ const Fuzzer = struct {
405 };405 };
406 const arena = self.arena_ctx.allocator();406 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|
409 panic("failed to open directory '{s}': {t}", .{ unit_test_name, e });409 panic("failed to open directory '{s}': {t}", .{ unit_test_name, e });
410 self.input = in: {410 self.input = in: {
411 const f = self.corpus_dir.createFile("in", .{411 const f = self.corpus_dir.createFile(io, "in", .{
412 .read = true,412 .read = true,
413 .truncate = false,413 .truncate = false,
414 // In case any other fuzz tests are running under the same test name,414 // In case any other fuzz tests are running under the same test name,
...@@ -419,7 +419,7 @@ const Fuzzer = struct {...@@ -419,7 +419,7 @@ const Fuzzer = struct {
419 error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"),419 error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"),
420 else => panic("failed to create input file 'in': {t}", .{e}),420 else => panic("failed to create input file 'in': {t}", .{e}),
421 };421 };
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});
423 const map = (if (size < std.heap.page_size_max)423 const map = (if (size < std.heap.page_size_max)
424 MemoryMappedList.create(f, 8, std.heap.page_size_max)424 MemoryMappedList.create(f, 8, std.heap.page_size_max)
425 else425 else
...@@ -445,6 +445,7 @@ const Fuzzer = struct {...@@ -445,6 +445,7 @@ const Fuzzer = struct {
445 while (true) {445 while (true) {
446 var name_buf: [@sizeOf(usize) * 2]u8 = undefined;446 var name_buf: [@sizeOf(usize) * 2]u8 = undefined;
447 const bytes = self.corpus_dir.readFileAlloc(447 const bytes = self.corpus_dir.readFileAlloc(
448 io,
448 std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable,449 std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable,
449 arena,450 arena,
450 .unlimited,451 .unlimited,
...@@ -466,7 +467,7 @@ const Fuzzer = struct {...@@ -466,7 +467,7 @@ const Fuzzer = struct {
466 self.input.deinit();467 self.input.deinit();
467 self.corpus.deinit(gpa);468 self.corpus.deinit(gpa);
468 self.mutations.deinit(gpa);469 self.mutations.deinit(gpa);
469 self.corpus_dir.close();470 self.corpus_dir.close(io);
470 self.arena_ctx.deinit();471 self.arena_ctx.deinit();
471 self.* = undefined;472 self.* = undefined;
472 }473 }
...@@ -573,17 +574,10 @@ const Fuzzer = struct {...@@ -573,17 +574,10 @@ const Fuzzer = struct {
573574
574 // Write new corpus to cache575 // Write new corpus to cache
575 var name_buf: [@sizeOf(usize) * 2]u8 = undefined;576 var name_buf: [@sizeOf(usize) * 2]u8 = undefined;
576 self.corpus_dir.writeFile(.{577 self.corpus_dir.writeFile(io, .{
577 .sub_path = std.fmt.bufPrint(578 .sub_path = std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable,
578 &name_buf,
579 "{x}",
580 .{self.corpus_dir_idx},
581 ) catch unreachable,
582 .data = bytes,579 .data = bytes,
583 }) catch |e| panic(580 }) catch |e| panic("failed to write corpus file '{x}': {t}", .{ self.corpus_dir_idx, e });
584 "failed to write corpus file '{x}': {t}",
585 .{ self.corpus_dir_idx, e },
586 );
587 self.corpus_dir_idx += 1;581 self.corpus_dir_idx += 1;
588 }582 }
589 }583 }
...@@ -1320,9 +1314,9 @@ pub const MemoryMappedList = struct {...@@ -1320,9 +1314,9 @@ pub const MemoryMappedList = struct {
1320 /// How many bytes this list can hold without allocating additional memory.1314 /// How many bytes this list can hold without allocating additional memory.
1321 capacity: usize,1315 capacity: usize,
1322 /// The file is kept open so that it can be resized.1316 /// 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 {
1326 const ptr = try std.posix.mmap(1320 const ptr = try std.posix.mmap(
1327 null,1321 null,
1328 capacity,1322 capacity,
...@@ -1338,13 +1332,13 @@ pub const MemoryMappedList = struct {...@@ -1338,13 +1332,13 @@ pub const MemoryMappedList = struct {
1338 };1332 };
1339 }1333 }
13401334
1341 pub fn create(file: std.fs.File, length: usize, capacity: usize) !MemoryMappedList {1335 pub fn create(file: Io.File, length: usize, capacity: usize) !MemoryMappedList {
1342 try file.setEndPos(capacity);1336 try file.setLength(io, capacity);
1343 return init(file, length, capacity);1337 return init(file, length, capacity);
1344 }1338 }
13451339
1346 pub fn deinit(l: *MemoryMappedList) void {1340 pub fn deinit(l: *MemoryMappedList) void {
1347 l.file.close();1341 l.file.close(io);
1348 std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));1342 std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));
1349 l.* = undefined;1343 l.* = undefined;
1350 }1344 }
...@@ -1369,7 +1363,7 @@ pub const MemoryMappedList = struct {...@@ -1369,7 +1363,7 @@ pub const MemoryMappedList = struct {
1369 if (l.capacity >= new_capacity) return;1363 if (l.capacity >= new_capacity) return;
13701364
1371 std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));1365 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);
1373 l.* = try init(l.file, l.items.len, new_capacity);1367 l.* = try init(l.file, l.items.len, new_capacity);
1374 }1368 }
13751369
lib/init/src/main.zig+24-2
...@@ -1,10 +1,32 @@...@@ -1,10 +1,32 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
3
2const _NAME = @import(".NAME");4const _NAME = @import(".NAME");
35
4pub fn main() !void {6pub fn main() !void {
5 // Prints to stderr, ignoring potential errors.7 // Prints to stderr, unbuffered, ignoring potential errors.
6 std.debug.print("All your {s} are belong to us.\n", .{"codebase"});8 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!
8}30}
931
10test "simple test" {32test "simple test" {
lib/init/src/root.zig+7-12
...@@ -1,17 +1,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.
2const std = @import("std");2const std = @import("std");
3const Io = std.Io;
34
4pub fn bufferedPrint() !void {5/// This is a documentation comment to explain the `printAnotherMessage` function below.
5 // Stdout is for the actual output of your application, for example if you6///
6 // are implementing gzip, then only the compressed bytes should be sent to7/// Accepting an `Io.Writer` instance is a handy way to write reusable code.
7 // stdout, not any debugging messages.8pub fn printAnotherMessage(writer: *Io.Writer) Io.Writer.Error!void {
8 var stdout_buffer: [1024]u8 = undefined;9 try writer.print("Run `zig build test` to run the tests.\n", .{});
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!
15}10}
1611
17pub fn add(a: i32, b: i32) i32 {12pub fn add(a: i32, b: i32) i32 {
lib/std/Build.zig+83-61
...@@ -1,21 +1,20 @@...@@ -1,21 +1,20 @@
1const Build = @This();
1const builtin = @import("builtin");2const builtin = @import("builtin");
23
3const std = @import("std.zig");4const std = @import("std.zig");
4const Io = std.Io;5const Io = std.Io;
5const fs = std.fs;6const fs = std.fs;
6const mem = std.mem;7const mem = std.mem;
7const debug = std.debug;
8const panic = std.debug.panic;8const panic = std.debug.panic;
9const assert = debug.assert;9const assert = std.debug.assert;
10const log = std.log;10const log = std.log;
11const StringHashMap = std.StringHashMap;11const StringHashMap = std.StringHashMap;
12const Allocator = mem.Allocator;12const Allocator = std.mem.Allocator;
13const Target = std.Target;13const Target = std.Target;
14const process = std.process;14const process = std.process;
15const EnvMap = std.process.EnvMap;15const EnvMap = std.process.EnvMap;
16const File = fs.File;16const File = std.Io.File;
17const Sha256 = std.crypto.hash.sha2.Sha256;17const Sha256 = std.crypto.hash.sha2.Sha256;
18const Build = @This();
19const ArrayList = std.ArrayList;18const ArrayList = std.ArrayList;
2019
21pub const Cache = @import("Build/Cache.zig");20pub const Cache = @import("Build/Cache.zig");
...@@ -130,6 +129,9 @@ pub const Graph = struct {...@@ -130,6 +129,9 @@ pub const Graph = struct {
130 dependency_cache: InitializedDepMap = .empty,129 dependency_cache: InitializedDepMap = .empty,
131 allow_so_scripts: ?bool = null,130 allow_so_scripts: ?bool = null,
132 time_report: bool,131 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,
133};135};
134136
135const AvailableDeps = []const struct { []const u8, []const u8 };137const AvailableDeps = []const struct { []const u8, []const u8 };
...@@ -1699,21 +1701,20 @@ pub fn addCheckFile(...@@ -1699,21 +1701,20 @@ pub fn addCheckFile(
1699 return Step.CheckFile.create(b, file_source, options);1701 return Step.CheckFile.create(b, file_source, options);
1700}1702}
17011703
1702pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Dir.StatFileError)!void {1704pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.CreateDirError || Io.Dir.StatFileError)!void {
1703 if (b.verbose) {1705 const io = b.graph.io;
1704 log.info("truncate {s}", .{dest_path});1706 if (b.verbose) log.info("truncate {s}", .{dest_path});
1705 }1707 const cwd = Io.Dir.cwd();
1706 const cwd = fs.cwd();1708 var src_file = cwd.createFile(io, dest_path, .{}) catch |err| switch (err) {
1707 var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) {
1708 error.FileNotFound => blk: {1709 error.FileNotFound => blk: {
1709 if (fs.path.dirname(dest_path)) |dirname| {1710 if (fs.path.dirname(dest_path)) |dirname| {
1710 try cwd.makePath(dirname);1711 try cwd.createDirPath(io, dirname);
1711 }1712 }
1712 break :blk try cwd.createFile(dest_path, .{});1713 break :blk try cwd.createFile(io, dest_path, .{});
1713 },1714 },
1714 else => |e| return e,1715 else => |e| return e,
1715 };1716 };
1716 src_file.close();1717 src_file.close(io);
1717}1718}
17181719
1719/// References a file or directory relative to the source root.1720/// References a file or directory relative to the source root.
...@@ -1761,7 +1762,10 @@ fn supportedWindowsProgramExtension(ext: []const u8) bool {...@@ -1761,7 +1762,10 @@ fn supportedWindowsProgramExtension(ext: []const u8) bool {
1761}1762}
17621763
1763fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {1764fn 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| {
1765 return p;1769 return p;
1766 } else |err| switch (err) {1770 } else |err| switch (err) {
1767 error.OutOfMemory => @panic("OOM"),1771 error.OutOfMemory => @panic("OOM"),
...@@ -1775,7 +1779,11 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {...@@ -1775,7 +1779,11 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {
1775 while (it.next()) |ext| {1779 while (it.next()) |ext| {
1776 if (!supportedWindowsProgramExtension(ext)) continue;1780 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) {
1779 error.OutOfMemory => @panic("OOM"),1787 error.OutOfMemory => @panic("OOM"),
1780 else => continue,1788 else => continue,
1781 };1789 };
...@@ -1839,7 +1847,7 @@ pub fn runAllowFail(...@@ -1839,7 +1847,7 @@ pub fn runAllowFail(
1839 child.env_map = &b.graph.env_map;1847 child.env_map = &b.graph.env_map;
18401848
1841 try Step.handleVerbose2(b, null, child.env_map, argv);1849 try Step.handleVerbose2(b, null, child.env_map, argv);
1842 try child.spawn();1850 try child.spawn(io);
18431851
1844 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});1852 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
1845 const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {1853 const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {
...@@ -1847,7 +1855,7 @@ pub fn runAllowFail(...@@ -1847,7 +1855,7 @@ pub fn runAllowFail(
1847 };1855 };
1848 errdefer b.allocator.free(stdout);1856 errdefer b.allocator.free(stdout);
18491857
1850 const term = try child.wait();1858 const term = try child.wait(io);
1851 switch (term) {1859 switch (term) {
1852 .Exited => |code| {1860 .Exited => |code| {
1853 if (code != 0) {1861 if (code != 0) {
...@@ -2091,7 +2099,7 @@ pub fn dependencyFromBuildZig(...@@ -2091,7 +2099,7 @@ pub fn dependencyFromBuildZig(
2091 }2099 }
20922100
2093 const full_path = b.pathFromRoot("build.zig.zon");2101 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 });
2095}2103}
20962104
2097fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {2105fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {
...@@ -2185,6 +2193,7 @@ fn dependencyInner(...@@ -2185,6 +2193,7 @@ fn dependencyInner(
2185 pkg_deps: AvailableDeps,2193 pkg_deps: AvailableDeps,
2186 args: anytype,2194 args: anytype,
2187) *Dependency {2195) *Dependency {
2196 const io = b.graph.io;
2188 const user_input_options = userInputOptionsFromArgs(b.allocator, args);2197 const user_input_options = userInputOptionsFromArgs(b.allocator, args);
2189 if (b.graph.dependency_cache.getContext(.{2198 if (b.graph.dependency_cache.getContext(.{
2190 .build_root_string = build_root_string,2199 .build_root_string = build_root_string,
...@@ -2194,7 +2203,7 @@ fn dependencyInner(...@@ -2194,7 +2203,7 @@ fn dependencyInner(
21942203
2195 const build_root: std.Build.Cache.Directory = .{2204 const build_root: std.Build.Cache.Directory = .{
2196 .path = build_root_string,2205 .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| {
2198 std.debug.print("unable to open '{s}': {s}\n", .{2207 std.debug.print("unable to open '{s}': {s}\n", .{
2199 build_root_string, @errorName(err),2208 build_root_string, @errorName(err),
2200 });2209 });
...@@ -2239,7 +2248,7 @@ pub const GeneratedFile = struct {...@@ -2239,7 +2248,7 @@ pub const GeneratedFile = struct {
2239 /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards.2248 /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards.
2240 path: ?[]const u8 = null,2249 path: ?[]const u8 = null,
22412250
2242 /// Deprecated, see `getPath2`.2251 /// Deprecated, see `getPath3`.
2243 pub fn getPath(gen: GeneratedFile) []const u8 {2252 pub fn getPath(gen: GeneratedFile) []const u8 {
2244 return gen.step.owner.pathFromCwd(gen.path orelse std.debug.panic(2253 return gen.step.owner.pathFromCwd(gen.path orelse std.debug.panic(
2245 "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?",2254 "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 {...@@ -2247,11 +2256,19 @@ pub const GeneratedFile = struct {
2247 ));2256 ));
2248 }2257 }
22492258
2259 /// Deprecated, see `getPath3`.
2250 pub fn getPath2(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) []const u8 {2260 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 {
2251 return gen.path orelse {2267 return gen.path orelse {
2252 const w, const ttyconf = debug.lockStderrWriter(&.{});2268 const graph = gen.step.owner.graph;
2253 dumpBadGetPathHelp(gen.step, w, ttyconf, src_builder, asking_step) catch {};2269 const io = graph.io;
2254 debug.unlockStderrWriter();2270 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2271 dumpBadGetPathHelp(gen.step, stderr.terminal(), src_builder, asking_step) catch {};
2255 @panic("misconfigured build script");2272 @panic("misconfigured build script");
2256 };2273 };
2257 }2274 }
...@@ -2426,22 +2443,29 @@ pub const LazyPath = union(enum) {...@@ -2426,22 +2443,29 @@ pub const LazyPath = union(enum) {
2426 }2443 }
2427 }2444 }
24282445
2429 /// Deprecated, see `getPath3`.2446 /// Deprecated, see `getPath4`.
2430 pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 {2447 pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 {
2431 return getPath2(lazy_path, src_builder, null);2448 return getPath2(lazy_path, src_builder, null);
2432 }2449 }
24332450
2434 /// Deprecated, see `getPath3`.2451 /// Deprecated, see `getPath4`.
2435 pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {2452 pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
2436 const p = getPath3(lazy_path, src_builder, asking_step);2453 const p = getPath3(lazy_path, src_builder, asking_step);
2437 return src_builder.pathResolve(&.{ p.root_dir.path orelse ".", p.sub_path });2454 return src_builder.pathResolve(&.{ p.root_dir.path orelse ".", p.sub_path });
2438 }2455 }
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
2440 /// Intended to be used during the make phase only.2464 /// Intended to be used during the make phase only.
2441 ///2465 ///
2442 /// `asking_step` is only used for debugging purposes; it's the step being2466 /// `asking_step` is only used for debugging purposes; it's the step being
2443 /// run that is asking for the path.2467 /// 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 {
2445 switch (lazy_path) {2469 switch (lazy_path) {
2446 .src_path => |sp| return .{2470 .src_path => |sp| return .{
2447 .root_dir = sp.owner.build_root,2471 .root_dir = sp.owner.build_root,
...@@ -2455,12 +2479,15 @@ pub const LazyPath = union(enum) {...@@ -2455,12 +2479,15 @@ pub const LazyPath = union(enum) {
2455 // TODO make gen.file.path not be absolute and use that as the2479 // TODO make gen.file.path not be absolute and use that as the
2456 // basis for not traversing up too many directories.2480 // basis for not traversing up too many directories.
24572481
2482 const graph = src_builder.graph;
2483
2458 var file_path: Cache.Path = .{2484 var file_path: Cache.Path = .{
2459 .root_dir = Cache.Directory.cwd(),2485 .root_dir = Cache.Directory.cwd(),
2460 .sub_path = gen.file.path orelse {2486 .sub_path = gen.file.path orelse {
2461 const w, const ttyconf = debug.lockStderrWriter(&.{});2487 const io = graph.io;
2462 dumpBadGetPathHelp(gen.file.step, w, ttyconf, src_builder, asking_step) catch {};2488 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
2463 debug.unlockStderrWriter();2489 dumpBadGetPathHelp(gen.file.step, stderr.terminal(), src_builder, asking_step) catch {};
2490 io.unlockStderr();
2464 @panic("misconfigured build script");2491 @panic("misconfigured build script");
2465 },2492 },
2466 };2493 };
...@@ -2550,40 +2577,36 @@ fn dumpBadDirnameHelp(...@@ -2550,40 +2577,36 @@ fn dumpBadDirnameHelp(
2550 comptime msg: []const u8,2577 comptime msg: []const u8,
2551 args: anytype,2578 args: anytype,
2552) anyerror!void {2579) anyerror!void {
2553 const w, const tty_config = debug.lockStderrWriter(&.{});2580 const stderr = std.debug.lockStderr(&.{}).terminal();
2554 defer debug.unlockStderrWriter();2581 defer std.debug.unlockStderr();
2582 const w = stderr.writer;
25552583
2556 try w.print(msg, args);2584 try w.print(msg, args);
25572585
2558 if (fail_step) |s| {2586 if (fail_step) |s| {
2559 tty_config.setColor(w, .red) catch {};2587 stderr.setColor(.red) catch {};
2560 try w.writeAll(" The step was created by this stack trace:\n");2588 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);
2564 }2592 }
25652593
2566 if (asking_step) |as| {2594 if (asking_step) |as| {
2567 tty_config.setColor(w, .red) catch {};2595 stderr.setColor(.red) catch {};
2568 try w.print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name});2596 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);
2572 }2600 }
25732601
2574 tty_config.setColor(w, .red) catch {};2602 stderr.setColor(.red) catch {};
2575 try w.writeAll(" Hope that helps. Proceeding to panic.\n");2603 try w.writeAll(" Proceeding to panic.\n");
2576 tty_config.setColor(w, .reset) catch {};2604 stderr.setColor(.reset) catch {};
2577}2605}
25782606
2579/// In this function the stderr mutex has already been locked.2607/// In this function the stderr mutex has already been locked.
2580pub fn dumpBadGetPathHelp(2608pub fn dumpBadGetPathHelp(s: *Step, t: Io.Terminal, src_builder: *Build, asking_step: ?*Step) anyerror!void {
2581 s: *Step,2609 const w = t.writer;
2582 w: *std.Io.Writer,
2583 tty_config: std.Io.tty.Config,
2584 src_builder: *Build,
2585 asking_step: ?*Step,
2586) anyerror!void {
2587 try w.print(2610 try w.print(
2588 \\getPath() was called on a GeneratedFile that wasn't built yet.2611 \\getPath() was called on a GeneratedFile that wasn't built yet.
2589 \\ source package path: {s}2612 \\ source package path: {s}
...@@ -2594,21 +2617,21 @@ pub fn dumpBadGetPathHelp(...@@ -2594,21 +2617,21 @@ pub fn dumpBadGetPathHelp(
2594 s.name,2617 s.name,
2595 });2618 });
25962619
2597 tty_config.setColor(w, .red) catch {};2620 t.setColor(.red) catch {};
2598 try w.writeAll(" The step was created by this stack trace:\n");2621 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);
2602 if (asking_step) |as| {2625 if (asking_step) |as| {
2603 tty_config.setColor(w, .red) catch {};2626 t.setColor(.red) catch {};
2604 try w.print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name});2627 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);
2608 }2631 }
2609 tty_config.setColor(w, .red) catch {};2632 t.setColor(.red) catch {};
2610 try w.writeAll(" Hope that helps. Proceeding to panic.\n");2633 try w.writeAll(" Proceeding to panic.\n");
2611 tty_config.setColor(w, .reset) catch {};2634 t.setColor(.reset) catch {};
2612}2635}
26132636
2614pub const InstallDir = union(enum) {2637pub const InstallDir = union(enum) {
...@@ -2634,13 +2657,12 @@ pub const InstallDir = union(enum) {...@@ -2634,13 +2657,12 @@ pub const InstallDir = union(enum) {
2634/// source of API breakage in the future, so keep that in mind when using this2657/// source of API breakage in the future, so keep that in mind when using this
2635/// function.2658/// function.
2636pub fn makeTempPath(b: *Build) []const u8 {2659pub fn makeTempPath(b: *Build) []const u8 {
2660 const io = b.graph.io;
2637 const rand_int = std.crypto.random.int(u64);2661 const rand_int = std.crypto.random.int(u64);
2638 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);2662 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
2639 const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM");2663 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| {2664 b.cache_root.handle.createDirPath(io, tmp_dir_sub_path) catch |err| {
2641 std.debug.print("unable to make tmp path '{s}': {s}\n", .{2665 std.debug.print("unable to make tmp path '{s}': {t}\n", .{ result_path, err });
2642 result_path, @errorName(err),
2643 });
2644 };2666 };
2645 return result_path;2667 return result_path;
2646}2668}
lib/std/Build/Cache.zig+103-128
...@@ -8,7 +8,6 @@ const builtin = @import("builtin");...@@ -8,7 +8,6 @@ const builtin = @import("builtin");
8const std = @import("std");8const std = @import("std");
9const Io = std.Io;9const Io = std.Io;
10const crypto = std.crypto;10const crypto = std.crypto;
11const fs = std.fs;
12const assert = std.debug.assert;11const assert = std.debug.assert;
13const testing = std.testing;12const testing = std.testing;
14const mem = std.mem;13const mem = std.mem;
...@@ -18,7 +17,7 @@ const log = std.log.scoped(.cache);...@@ -18,7 +17,7 @@ const log = std.log.scoped(.cache);
1817
19gpa: Allocator,18gpa: Allocator,
20io: Io,19io: Io,
21manifest_dir: fs.Dir,20manifest_dir: Io.Dir,
22hash: HashHelper = .{},21hash: HashHelper = .{},
23/// This value is accessed from multiple threads, protected by mutex.22/// This value is accessed from multiple threads, protected by mutex.
24recent_problematic_timestamp: Io.Timestamp = .zero,23recent_problematic_timestamp: Io.Timestamp = .zero,
...@@ -71,7 +70,7 @@ const PrefixedPath = struct {...@@ -71,7 +70,7 @@ const PrefixedPath = struct {
7170
72fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {71fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {
73 const gpa = cache.gpa;72 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});
75 errdefer gpa.free(resolved_path);74 errdefer gpa.free(resolved_path);
76 return findPrefixResolved(cache, resolved_path);75 return findPrefixResolved(cache, resolved_path);
77}76}
...@@ -102,9 +101,9 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {...@@ -102,9 +101,9 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
102}101}
103102
104fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {103fn 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);
106 errdefer allocator.free(relative);105 errdefer allocator.free(relative);
107 var component_iterator = fs.path.NativeComponentIterator.init(relative);106 var component_iterator = std.fs.path.NativeComponentIterator.init(relative);
108 if (component_iterator.root() != null) {107 if (component_iterator.root() != null) {
109 return error.NotASubPath;108 return error.NotASubPath;
110 }109 }
...@@ -145,17 +144,17 @@ pub const File = struct {...@@ -145,17 +144,17 @@ pub const File = struct {
145 max_file_size: ?usize,144 max_file_size: ?usize,
146 /// Populated if the user calls `addOpenedFile`.145 /// Populated if the user calls `addOpenedFile`.
147 /// The handle is not owned here.146 /// The handle is not owned here.
148 handle: ?fs.File,147 handle: ?Io.File,
149 stat: Stat,148 stat: Stat,
150 bin_digest: BinDigest,149 bin_digest: BinDigest,
151 contents: ?[]const u8,150 contents: ?[]const u8,
152151
153 pub const Stat = struct {152 pub const Stat = struct {
154 inode: fs.File.INode,153 inode: Io.File.INode,
155 size: u64,154 size: u64,
156 mtime: Io.Timestamp,155 mtime: Io.Timestamp,
157156
158 pub fn fromFs(fs_stat: fs.File.Stat) Stat {157 pub fn fromFs(fs_stat: Io.File.Stat) Stat {
159 return .{158 return .{
160 .inode = fs_stat.inode,159 .inode = fs_stat.inode,
161 .size = fs_stat.size,160 .size = fs_stat.size,
...@@ -178,7 +177,7 @@ pub const File = struct {...@@ -178,7 +177,7 @@ pub const File = struct {
178 file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new;177 file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new;
179 }178 }
180179
181 pub fn updateHandle(file: *File, new_handle: ?fs.File) void {180 pub fn updateHandle(file: *File, new_handle: ?Io.File) void {
182 const handle = new_handle orelse return;181 const handle = new_handle orelse return;
183 file.handle = handle;182 file.handle = handle;
184 }183 }
...@@ -293,16 +292,16 @@ pub fn binToHex(bin_digest: BinDigest) HexDigest {...@@ -293,16 +292,16 @@ pub fn binToHex(bin_digest: BinDigest) HexDigest {
293}292}
294293
295pub const Lock = struct {294pub 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 {
299 if (builtin.os.tag == .windows) {298 if (builtin.os.tag == .windows) {
300 // Windows does not guarantee that locks are immediately unlocked when299 // Windows does not guarantee that locks are immediately unlocked when
301 // the file handle is closed. See LockFileEx documentation.300 // the file handle is closed. See LockFileEx documentation.
302 lock.manifest_file.unlock();301 lock.manifest_file.unlock(io);
303 }302 }
304303
305 lock.manifest_file.close();304 lock.manifest_file.close(io);
306 lock.* = undefined;305 lock.* = undefined;
307 }306 }
308};307};
...@@ -311,7 +310,7 @@ pub const Manifest = struct {...@@ -311,7 +310,7 @@ pub const Manifest = struct {
311 cache: *Cache,310 cache: *Cache,
312 /// Current state for incremental hashing.311 /// Current state for incremental hashing.
313 hash: HashHelper,312 hash: HashHelper,
314 manifest_file: ?fs.File,313 manifest_file: ?Io.File,
315 manifest_dirty: bool,314 manifest_dirty: bool,
316 /// Set this flag to true before calling hit() in order to indicate that315 /// Set this flag to true before calling hit() in order to indicate that
317 /// upon a cache hit, the code using the cache will not modify the files316 /// upon a cache hit, the code using the cache will not modify the files
...@@ -332,9 +331,9 @@ pub const Manifest = struct {...@@ -332,9 +331,9 @@ pub const Manifest = struct {
332331
333 pub const Diagnostic = union(enum) {332 pub const Diagnostic = union(enum) {
334 none,333 none,
335 manifest_create: fs.File.OpenError,334 manifest_create: Io.File.OpenError,
336 manifest_read: fs.File.ReadError,335 manifest_read: Io.File.Reader.Error,
337 manifest_lock: fs.File.LockError,336 manifest_lock: Io.File.LockError,
338 file_open: FileOp,337 file_open: FileOp,
339 file_stat: FileOp,338 file_stat: FileOp,
340 file_read: FileOp,339 file_read: FileOp,
...@@ -393,10 +392,10 @@ pub const Manifest = struct {...@@ -393,10 +392,10 @@ pub const Manifest = struct {
393 }392 }
394393
395 /// Same as `addFilePath` except the file has already been opened.394 /// 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 {
397 const gpa = m.cache.gpa;396 const gpa = m.cache.gpa;
398 try m.files.ensureUnusedCapacity(gpa, 1);397 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, &.{
400 path.root_dir.path orelse ".",399 path.root_dir.path orelse ".",
401 path.subPathOrDot(),400 path.subPathOrDot(),
402 });401 });
...@@ -417,7 +416,7 @@ pub const Manifest = struct {...@@ -417,7 +416,7 @@ pub const Manifest = struct {
417 return addFileInner(self, prefixed_path, null, max_file_size);416 return addFileInner(self, prefixed_path, null, max_file_size);
418 }417 }
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 {
421 const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});420 const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});
422 if (gop.found_existing) {421 if (gop.found_existing) {
423 self.cache.gpa.free(prefixed_path.sub_path);422 self.cache.gpa.free(prefixed_path.sub_path);
...@@ -460,7 +459,7 @@ pub const Manifest = struct {...@@ -460,7 +459,7 @@ pub const Manifest = struct {
460 }459 }
461 }460 }
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 {
464 assert(self.manifest_file == null);463 assert(self.manifest_file == null);
465 return self.addDepFileMaybePost(dir, dep_file_sub_path);464 return self.addDepFileMaybePost(dir, dep_file_sub_path);
466 }465 }
...@@ -503,11 +502,13 @@ pub const Manifest = struct {...@@ -503,11 +502,13 @@ pub const Manifest = struct {
503 @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest);502 @memcpy(manifest_file_path[0..self.hex_digest.len], &self.hex_digest);
504 manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*;503 manifest_file_path[hex_digest_len..][0..ext.len].* = ext.*;
505504
505 const io = self.cache.io;
506
506 // We'll try to open the cache with an exclusive lock, but if that would block507 // We'll try to open the cache with an exclusive lock, but if that would block
507 // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll508 // and `want_shared_lock` is set, a shared lock might be sufficient, so we'll
508 // open with a shared lock instead.509 // open with a shared lock instead.
509 while (true) {510 while (true) {
510 if (self.cache.manifest_dir.createFile(&manifest_file_path, .{511 if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{
511 .read = true,512 .read = true,
512 .truncate = false,513 .truncate = false,
513 .lock = .exclusive,514 .lock = .exclusive,
...@@ -518,7 +519,7 @@ pub const Manifest = struct {...@@ -518,7 +519,7 @@ pub const Manifest = struct {
518 break;519 break;
519 } else |err| switch (err) {520 } else |err| switch (err) {
520 error.WouldBlock => {521 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, .{
522 .mode = .read_write,523 .mode = .read_write,
523 .lock = .shared,524 .lock = .shared,
524 }) catch |e| {525 }) catch |e| {
...@@ -542,7 +543,7 @@ pub const Manifest = struct {...@@ -542,7 +543,7 @@ pub const Manifest = struct {
542 return error.CacheCheckFailed;543 return error.CacheCheckFailed;
543 }544 }
544545
545 if (self.cache.manifest_dir.createFile(&manifest_file_path, .{546 if (self.cache.manifest_dir.createFile(io, &manifest_file_path, .{
546 .read = true,547 .read = true,
547 .truncate = false,548 .truncate = false,
548 .lock = .exclusive,549 .lock = .exclusive,
...@@ -702,7 +703,7 @@ pub const Manifest = struct {...@@ -702,7 +703,7 @@ pub const Manifest = struct {
702 const file_path = iter.rest();703 const file_path = iter.rest();
703704
704 const stat_size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat;705 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;
706 const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat;707 const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat;
707 const file_bin_digest = b: {708 const file_bin_digest = b: {
708 if (digest_str.len != hex_digest_len) return error.InvalidFormat;709 if (digest_str.len != hex_digest_len) return error.InvalidFormat;
...@@ -758,7 +759,7 @@ pub const Manifest = struct {...@@ -758,7 +759,7 @@ pub const Manifest = struct {
758759
759 const pp = cache_hash_file.prefixed_path;760 const pp = cache_hash_file.prefixed_path;
760 const dir = self.cache.prefixes()[pp.prefix].handle;761 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) {
762 error.FileNotFound => {763 error.FileNotFound => {
763 // Every digest before this one has been populated successfully.764 // Every digest before this one has been populated successfully.
764 return .{ .miss = .{ .file_digests_populated = idx } };765 return .{ .miss = .{ .file_digests_populated = idx } };
...@@ -772,9 +773,9 @@ pub const Manifest = struct {...@@ -772,9 +773,9 @@ pub const Manifest = struct {
772 return error.CacheCheckFailed;773 return error.CacheCheckFailed;
773 },774 },
774 };775 };
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| {
778 self.diagnostic = .{ .file_stat = .{779 self.diagnostic = .{ .file_stat = .{
779 .file_index = idx,780 .file_index = idx,
780 .err = err,781 .err = err,
...@@ -799,7 +800,7 @@ pub const Manifest = struct {...@@ -799,7 +800,7 @@ pub const Manifest = struct {
799 }800 }
800801
801 var actual_digest: BinDigest = undefined;802 var actual_digest: BinDigest = undefined;
802 hashFile(this_file, &actual_digest) catch |err| {803 hashFile(io, this_file, &actual_digest) catch |err| {
803 self.diagnostic = .{ .file_read = .{804 self.diagnostic = .{ .file_read = .{
804 .file_index = idx,805 .file_index = idx,
805 .err = err,806 .err = err,
...@@ -872,17 +873,17 @@ pub const Manifest = struct {...@@ -872,17 +873,17 @@ pub const Manifest = struct {
872 if (man.want_refresh_timestamp) {873 if (man.want_refresh_timestamp) {
873 man.want_refresh_timestamp = false;874 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", .{
876 .read = true,877 .read = true,
877 .truncate = true,878 .truncate = true,
878 }) catch |err| switch (err) {879 }) catch |err| switch (err) {
879 error.Canceled => return error.Canceled,880 error.Canceled => return error.Canceled,
880 else => return true,881 else => return true,
881 };882 };
882 defer file.close();883 defer file.close(io);
883884
884 // Save locally and also save globally (we still hold the global lock).885 // 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) {
886 error.Canceled => return error.Canceled,887 error.Canceled => return error.Canceled,
887 else => return true,888 else => return true,
888 };889 };
...@@ -894,19 +895,24 @@ pub const Manifest = struct {...@@ -894,19 +895,24 @@ pub const Manifest = struct {
894 }895 }
895896
896 fn populateFileHash(self: *Manifest, ch_file: *File) !void {897 fn populateFileHash(self: *Manifest, ch_file: *File) !void {
898 const io = self.cache.io;
899
897 if (ch_file.handle) |handle| {900 if (ch_file.handle) |handle| {
898 return populateFileHashHandle(self, ch_file, handle);901 return populateFileHashHandle(self, ch_file, handle);
899 } else {902 } else {
900 const pp = ch_file.prefixed_path;903 const pp = ch_file.prefixed_path;
901 const dir = self.cache.prefixes()[pp.prefix].handle;904 const dir = self.cache.prefixes()[pp.prefix].handle;
902 const handle = try dir.openFile(pp.sub_path, .{});905 const handle = try dir.openFile(io, pp.sub_path, .{});
903 defer handle.close();906 defer handle.close(io);
904 return populateFileHashHandle(self, ch_file, handle);907 return populateFileHashHandle(self, ch_file, handle);
905 }908 }
906 }909 }
907910
908 fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: fs.File) !void {911 fn populateFileHashHandle(self: *Manifest, ch_file: *File, io_file: Io.File) !void {
909 const actual_stat = try handle.stat();912 const io = self.cache.io;
913 const gpa = self.cache.gpa;
914
915 const actual_stat = try io_file.stat(io);
910 ch_file.stat = .{916 ch_file.stat = .{
911 .size = actual_stat.size,917 .size = actual_stat.size,
912 .mtime = actual_stat.mtime,918 .mtime = actual_stat.mtime,
...@@ -920,19 +926,17 @@ pub const Manifest = struct {...@@ -920,19 +926,17 @@ pub const Manifest = struct {
920 }926 }
921927
922 if (ch_file.max_file_size) |max_file_size| {928 if (ch_file.max_file_size) |max_file_size| {
923 if (ch_file.stat.size > max_file_size) {929 if (ch_file.stat.size > max_file_size) return error.FileTooBig;
924 return error.FileTooBig;
925 }
926930
927 const contents = try self.cache.gpa.alloc(u8, @as(usize, @intCast(ch_file.stat.size)));931 // Hash while reading from disk, to keep the contents in the cpu
928 errdefer self.cache.gpa.free(contents);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.
932 var hasher = hasher_init;936 var hasher = hasher_init;
933 var off: usize = 0;937 var off: usize = 0;
934 while (true) {938 while (true) {
935 const bytes_read = try handle.pread(contents[off..], off);939 const bytes_read = try io_file.readPositional(io, &.{contents[off..]}, off);
936 if (bytes_read == 0) break;940 if (bytes_read == 0) break;
937 hasher.update(contents[off..][0..bytes_read]);941 hasher.update(contents[off..][0..bytes_read]);
938 off += bytes_read;942 off += bytes_read;
...@@ -941,7 +945,7 @@ pub const Manifest = struct {...@@ -941,7 +945,7 @@ pub const Manifest = struct {
941945
942 ch_file.contents = contents;946 ch_file.contents = contents;
943 } else {947 } else {
944 try hashFile(handle, &ch_file.bin_digest);948 try hashFile(io, io_file, &ch_file.bin_digest);
945 }949 }
946950
947 self.hash.hasher.update(&ch_file.bin_digest);951 self.hash.hasher.update(&ch_file.bin_digest);
...@@ -1064,14 +1068,15 @@ pub const Manifest = struct {...@@ -1064,14 +1068,15 @@ pub const Manifest = struct {
1064 self.hash.hasher.update(&new_file.bin_digest);1068 self.hash.hasher.update(&new_file.bin_digest);
1065 }1069 }
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 {
1068 assert(self.manifest_file != null);1072 assert(self.manifest_file != null);
1069 return self.addDepFileMaybePost(dir, dep_file_sub_path);1073 return self.addDepFileMaybePost(dir, dep_file_sub_path);
1070 }1074 }
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 {
1073 const gpa = self.cache.gpa;1077 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));
1075 defer gpa.free(dep_file_contents);1080 defer gpa.free(dep_file_contents);
10761081
1077 var error_buf: std.ArrayList(u8) = .empty;1082 var error_buf: std.ArrayList(u8) = .empty;
...@@ -1130,13 +1135,13 @@ pub const Manifest = struct {...@@ -1130,13 +1135,13 @@ pub const Manifest = struct {
1130 /// lock from exclusive to shared.1135 /// lock from exclusive to shared.
1131 pub fn writeManifest(self: *Manifest) !void {1136 pub fn writeManifest(self: *Manifest) !void {
1132 assert(self.have_exclusive_lock);1137 assert(self.have_exclusive_lock);
11331138 const io = self.cache.io;
1134 const manifest_file = self.manifest_file.?;1139 const manifest_file = self.manifest_file.?;
1135 if (self.manifest_dirty) {1140 if (self.manifest_dirty) {
1136 self.manifest_dirty = false;1141 self.manifest_dirty = false;
11371142
1138 var buffer: [4000]u8 = undefined;1143 var buffer: [4000]u8 = undefined;
1139 var fw = manifest_file.writer(&buffer);1144 var fw = manifest_file.writer(io, &buffer);
1140 writeDirtyManifestToStream(self, &fw) catch |err| switch (err) {1145 writeDirtyManifestToStream(self, &fw) catch |err| switch (err) {
1141 error.WriteFailed => return fw.err.?,1146 error.WriteFailed => return fw.err.?,
1142 else => |e| return e,1147 else => |e| return e,
...@@ -1148,7 +1153,7 @@ pub const Manifest = struct {...@@ -1148,7 +1153,7 @@ pub const Manifest = struct {
1148 }1153 }
1149 }1154 }
11501155
1151 fn writeDirtyManifestToStream(self: *Manifest, fw: *fs.File.Writer) !void {1156 fn writeDirtyManifestToStream(self: *Manifest, fw: *Io.File.Writer) !void {
1152 try fw.interface.writeAll(manifest_header ++ "\n");1157 try fw.interface.writeAll(manifest_header ++ "\n");
1153 for (self.files.keys()) |file| {1158 for (self.files.keys()) |file| {
1154 try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{1159 try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{
...@@ -1165,13 +1170,11 @@ pub const Manifest = struct {...@@ -1165,13 +1170,11 @@ pub const Manifest = struct {
11651170
1166 fn downgradeToSharedLock(self: *Manifest) !void {1171 fn downgradeToSharedLock(self: *Manifest) !void {
1167 if (!self.have_exclusive_lock) return;1172 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.1175 if (std.process.can_spawn or !builtin.single_threaded) {
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) {
1173 const manifest_file = self.manifest_file.?;1176 const manifest_file = self.manifest_file.?;
1174 try manifest_file.downgradeLock();1177 try manifest_file.downgradeLock(io);
1175 }1178 }
11761179
1177 self.have_exclusive_lock = false;1180 self.have_exclusive_lock = false;
...@@ -1180,16 +1183,14 @@ pub const Manifest = struct {...@@ -1180,16 +1183,14 @@ pub const Manifest = struct {
1180 fn upgradeToExclusiveLock(self: *Manifest) error{CacheCheckFailed}!bool {1183 fn upgradeToExclusiveLock(self: *Manifest) error{CacheCheckFailed}!bool {
1181 if (self.have_exclusive_lock) return false;1184 if (self.have_exclusive_lock) return false;
1182 assert(self.manifest_file != null);1185 assert(self.manifest_file != null);
1186 const io = self.cache.io;
11831187
1184 // WASI does not currently support flock, so we bypass it here.1188 if (std.process.can_spawn or !builtin.single_threaded) {
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 const manifest_file = self.manifest_file.?;1189 const manifest_file = self.manifest_file.?;
1189 // Here we intentionally have a period where the lock is released, in case there are1190 // Here we intentionally have a period where the lock is released, in case there are
1190 // other processes holding a shared lock.1191 // other processes holding a shared lock.
1191 manifest_file.unlock();1192 manifest_file.unlock(io);
1192 manifest_file.lock(.exclusive) catch |err| {1193 manifest_file.lock(io, .exclusive) catch |err| {
1193 self.diagnostic = .{ .manifest_lock = err };1194 self.diagnostic = .{ .manifest_lock = err };
1194 return error.CacheCheckFailed;1195 return error.CacheCheckFailed;
1195 };1196 };
...@@ -1202,25 +1203,23 @@ pub const Manifest = struct {...@@ -1202,25 +1203,23 @@ pub const Manifest = struct {
1202 /// The `Manifest` remains safe to deinit.1203 /// The `Manifest` remains safe to deinit.
1203 /// Don't forget to call `writeManifest` before this!1204 /// Don't forget to call `writeManifest` before this!
1204 pub fn toOwnedLock(self: *Manifest) Lock {1205 pub fn toOwnedLock(self: *Manifest) Lock {
1205 const lock: Lock = .{1206 defer self.manifest_file = null;
1206 .manifest_file = self.manifest_file.?,1207 return .{ .manifest_file = self.manifest_file.? };
1207 };
1208
1209 self.manifest_file = null;
1210 return lock;
1211 }1208 }
12121209
1213 /// Releases the manifest file and frees any memory the Manifest was using.1210 /// Releases the manifest file and frees any memory the Manifest was using.
1214 /// `Manifest.hit` must be called first.1211 /// `Manifest.hit` must be called first.
1215 /// Don't forget to call `writeManifest` before this!1212 /// Don't forget to call `writeManifest` before this!
1216 pub fn deinit(self: *Manifest) void {1213 pub fn deinit(self: *Manifest) void {
1214 const io = self.cache.io;
1215
1217 if (self.manifest_file) |file| {1216 if (self.manifest_file) |file| {
1218 if (builtin.os.tag == .windows) {1217 if (builtin.os.tag == .windows) {
1219 // See Lock.release for why this is required on Windows1218 // See Lock.release for why this is required on Windows
1220 file.unlock();1219 file.unlock(io);
1221 }1220 }
12221221
1223 file.close();1222 file.close(io);
1224 }1223 }
1225 for (self.files.keys()) |*file| {1224 for (self.files.keys()) |*file| {
1226 file.deinit(self.cache.gpa);1225 file.deinit(self.cache.gpa);
...@@ -1278,57 +1277,33 @@ pub const Manifest = struct {...@@ -1278,57 +1277,33 @@ pub const Manifest = struct {
1278 }1277 }
1279};1278};
12801279
1281/// On operating systems that support symlinks, does a readlink. On other operating systems,1280fn hashFile(io: Io, file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.ReadPositionalError!void {
1282/// uses the file contents. Windows supports symlinks but only with elevated privileges, so1281 var buffer: [2048]u8 = undefined;
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;
1307 var hasher = hasher_init;1282 var hasher = hasher_init;
1308 var off: u64 = 0;1283 var offset: u64 = 0;
1309 while (true) {1284 while (true) {
1310 const bytes_read = try file.pread(&buf, off);1285 const n = try file.readPositional(io, &.{&buffer}, offset);
1311 if (bytes_read == 0) break;1286 if (n == 0) break;
1312 hasher.update(buf[0..bytes_read]);1287 hasher.update(buffer[0..n]);
1313 off += bytes_read;1288 offset += n;
1314 }1289 }
1315 hasher.final(bin_digest);1290 hasher.final(bin_digest);
1316}1291}
13171292
1318// Create/Write a file, close it, then grab its stat.mtime timestamp.1293// 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 {
1320 const test_out_file = "test-filetimestamp.tmp";1295 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, .{
1323 .read = true,1298 .read = true,
1324 .truncate = true,1299 .truncate = true,
1325 });1300 });
1326 defer {1301 defer {
1327 file.close();1302 file.close(io);
1328 dir.deleteFile(test_out_file) catch {};1303 dir.deleteFile(io, test_out_file) catch {};
1329 }1304 }
13301305
1331 return (try file.stat()).mtime;1306 return (try file.stat(io)).mtime;
1332}1307}
13331308
1334test "cache file and then recall it" {1309test "cache file and then recall it" {
...@@ -1340,11 +1315,11 @@ test "cache file and then recall it" {...@@ -1340,11 +1315,11 @@ test "cache file and then recall it" {
1340 const temp_file = "test.txt";1315 const temp_file = "test.txt";
1341 const temp_manifest_dir = "temp_manifest_dir";1316 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
1345 // Wait for file timestamps to tick1320 // Wait for file timestamps to tick
1346 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);1321 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
1347 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {1322 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1348 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);1323 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1349 }1324 }
13501325
...@@ -1355,10 +1330,10 @@ test "cache file and then recall it" {...@@ -1355,10 +1330,10 @@ test "cache file and then recall it" {
1355 var cache: Cache = .{1330 var cache: Cache = .{
1356 .io = io,1331 .io = io,
1357 .gpa = testing.allocator,1332 .gpa = testing.allocator,
1358 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),1333 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1359 };1334 };
1360 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1335 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1361 defer cache.manifest_dir.close();1336 defer cache.manifest_dir.close(io);
13621337
1363 {1338 {
1364 var ch = cache.obtain();1339 var ch = cache.obtain();
...@@ -1406,11 +1381,11 @@ test "check that changing a file makes cache fail" {...@@ -1406,11 +1381,11 @@ test "check that changing a file makes cache fail" {
1406 const original_temp_file_contents = "Hello, world!\n";1381 const original_temp_file_contents = "Hello, world!\n";
1407 const updated_temp_file_contents = "Hello, world; but updated!\n";1382 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
1411 // Wait for file timestamps to tick1386 // Wait for file timestamps to tick
1412 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);1387 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
1413 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {1388 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1414 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);1389 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1415 }1390 }
14161391
...@@ -1421,10 +1396,10 @@ test "check that changing a file makes cache fail" {...@@ -1421,10 +1396,10 @@ test "check that changing a file makes cache fail" {
1421 var cache: Cache = .{1396 var cache: Cache = .{
1422 .io = io,1397 .io = io,
1423 .gpa = testing.allocator,1398 .gpa = testing.allocator,
1424 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),1399 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1425 };1400 };
1426 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1401 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1427 defer cache.manifest_dir.close();1402 defer cache.manifest_dir.close(io);
14281403
1429 {1404 {
1430 var ch = cache.obtain();1405 var ch = cache.obtain();
...@@ -1443,7 +1418,7 @@ test "check that changing a file makes cache fail" {...@@ -1443,7 +1418,7 @@ test "check that changing a file makes cache fail" {
1443 try ch.writeManifest();1418 try ch.writeManifest();
1444 }1419 }
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
1448 {1423 {
1449 var ch = cache.obtain();1424 var ch = cache.obtain();
...@@ -1481,10 +1456,10 @@ test "no file inputs" {...@@ -1481,10 +1456,10 @@ test "no file inputs" {
1481 var cache: Cache = .{1456 var cache: Cache = .{
1482 .io = io,1457 .io = io,
1483 .gpa = testing.allocator,1458 .gpa = testing.allocator,
1484 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),1459 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1485 };1460 };
1486 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1461 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1487 defer cache.manifest_dir.close();1462 defer cache.manifest_dir.close(io);
14881463
1489 {1464 {
1490 var man = cache.obtain();1465 var man = cache.obtain();
...@@ -1523,12 +1498,12 @@ test "Manifest with files added after initial hash work" {...@@ -1523,12 +1498,12 @@ test "Manifest with files added after initial hash work" {
1523 const temp_file2 = "cache_hash_post_file_test2.txt";1498 const temp_file2 = "cache_hash_post_file_test2.txt";
1524 const temp_manifest_dir = "cache_hash_post_file_manifest_dir";1499 const temp_manifest_dir = "cache_hash_post_file_manifest_dir";
15251500
1526 try tmp.dir.writeFile(.{ .sub_path = temp_file1, .data = "Hello, world!\n" });1501 try tmp.dir.writeFile(io, .{ .sub_path = temp_file1, .data = "Hello, world!\n" });
1527 try tmp.dir.writeFile(.{ .sub_path = temp_file2, .data = "Hello world the second!\n" });1502 try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second!\n" });
15281503
1529 // Wait for file timestamps to tick1504 // Wait for file timestamps to tick
1530 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);1505 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
1531 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {1506 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1532 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);1507 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1533 }1508 }
15341509
...@@ -1540,10 +1515,10 @@ test "Manifest with files added after initial hash work" {...@@ -1540,10 +1515,10 @@ test "Manifest with files added after initial hash work" {
1540 var cache: Cache = .{1515 var cache: Cache = .{
1541 .io = io,1516 .io = io,
1542 .gpa = testing.allocator,1517 .gpa = testing.allocator,
1543 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),1518 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1544 };1519 };
1545 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1520 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1546 defer cache.manifest_dir.close();1521 defer cache.manifest_dir.close(io);
15471522
1548 {1523 {
1549 var ch = cache.obtain();1524 var ch = cache.obtain();
...@@ -1575,11 +1550,11 @@ test "Manifest with files added after initial hash work" {...@@ -1575,11 +1550,11 @@ test "Manifest with files added after initial hash work" {
1575 try testing.expect(mem.eql(u8, &digest1, &digest2));1550 try testing.expect(mem.eql(u8, &digest1, &digest2));
15761551
1577 // Modify the file added after initial hash1552 // 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
1580 // Wait for file timestamps to tick1555 // Wait for file timestamps to tick
1581 const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);1556 const initial_time2 = try testGetCurrentFileTimestamp(io, tmp.dir);
1582 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time2.nanoseconds) {1557 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time2.nanoseconds) {
1583 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);1558 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1584 }1559 }
15851560
lib/std/Build/Cache/Directory.zig+8-6
...@@ -1,7 +1,9 @@...@@ -1,7 +1,9 @@
1const Directory = @This();1const Directory = @This();
2
2const std = @import("../../std.zig");3const std = @import("../../std.zig");
3const assert = std.debug.assert;4const Io = std.Io;
4const fs = std.fs;5const fs = std.fs;
6const assert = std.debug.assert;
5const fmt = std.fmt;7const fmt = std.fmt;
6const Allocator = std.mem.Allocator;8const Allocator = std.mem.Allocator;
79
...@@ -9,7 +11,7 @@ const Allocator = std.mem.Allocator;...@@ -9,7 +11,7 @@ const Allocator = std.mem.Allocator;
9/// directly, but it is needed when passing the directory to a child process.11/// directly, but it is needed when passing the directory to a child process.
10/// `null` means cwd.12/// `null` means cwd.
11path: ?[]const u8,13path: ?[]const u8,
12handle: fs.Dir,14handle: Io.Dir,
1315
14pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory {16pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory {
15 return .{17 return .{
...@@ -21,7 +23,7 @@ pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory {...@@ -21,7 +23,7 @@ pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory {
21pub fn cwd() Directory {23pub fn cwd() Directory {
22 return .{24 return .{
23 .path = null,25 .path = null,
24 .handle = fs.cwd(),26 .handle = .cwd(),
25 };27 };
26}28}
2729
...@@ -50,8 +52,8 @@ pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) !...@@ -50,8 +52,8 @@ pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) !
50/// Whether or not the handle should be closed, or the path should be freed52/// Whether or not the handle should be closed, or the path should be freed
51/// is determined by usage, however this function is provided for convenience53/// is determined by usage, however this function is provided for convenience
52/// if it happens to be what the caller needs.54/// if it happens to be what the caller needs.
53pub fn closeAndFree(self: *Directory, gpa: Allocator) void {55pub fn closeAndFree(self: *Directory, gpa: Allocator, io: Io) void {
54 self.handle.close();56 self.handle.close(io);
55 if (self.path) |p| gpa.free(p);57 if (self.path) |p| gpa.free(p);
56 self.* = undefined;58 self.* = undefined;
57}59}
...@@ -64,5 +66,5 @@ pub fn format(self: Directory, writer: *std.Io.Writer) std.Io.Writer.Error!void...@@ -64,5 +66,5 @@ pub fn format(self: Directory, writer: *std.Io.Writer) std.Io.Writer.Error!void
64}66}
6567
66pub fn eql(self: Directory, other: Directory) bool {68pub fn eql(self: Directory, other: Directory) bool {
67 return self.handle.fd == other.handle.fd;69 return self.handle.handle == other.handle.handle;
68}70}
lib/std/Build/Cache/Path.zig+22-24
...@@ -2,8 +2,8 @@ const Path = @This();...@@ -2,8 +2,8 @@ const Path = @This();
22
3const std = @import("../../std.zig");3const std = @import("../../std.zig");
4const Io = std.Io;4const Io = std.Io;
5const assert = std.debug.assert;
6const fs = std.fs;5const fs = std.fs;
6const assert = std.debug.assert;
7const Allocator = std.mem.Allocator;7const Allocator = std.mem.Allocator;
8const Cache = std.Build.Cache;8const Cache = std.Build.Cache;
99
...@@ -59,58 +59,56 @@ pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Erro...@@ -59,58 +59,56 @@ pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Erro
59 return p.root_dir.joinZ(gpa, parts);59 return p.root_dir.joinZ(gpa, parts);
60}60}
6161
62pub fn openFile(62pub fn openFile(p: Path, io: Io, sub_path: []const u8, flags: Io.File.OpenFlags) !Io.File {
63 p: Path,
64 sub_path: []const u8,
65 flags: fs.File.OpenFlags,
66) !fs.File {
67 var buf: [fs.max_path_bytes]u8 = undefined;63 var buf: [fs.max_path_bytes]u8 = undefined;
68 const joined_path = if (p.sub_path.len == 0) sub_path else p: {64 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
69 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{65 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
70 p.sub_path, sub_path,66 p.sub_path, sub_path,
71 }) catch return error.NameTooLong;67 }) catch return error.NameTooLong;
72 };68 };
73 return p.root_dir.handle.openFile(joined_path, flags);69 return p.root_dir.handle.openFile(io, joined_path, flags);
74}70}
7571
76pub fn openDir(72pub fn openDir(
77 p: Path,73 p: Path,
74 io: Io,
78 sub_path: []const u8,75 sub_path: []const u8,
79 args: fs.Dir.OpenOptions,76 args: Io.Dir.OpenOptions,
80) fs.Dir.OpenError!fs.Dir {77) Io.Dir.OpenError!Io.Dir {
81 var buf: [fs.max_path_bytes]u8 = undefined;78 var buf: [fs.max_path_bytes]u8 = undefined;
82 const joined_path = if (p.sub_path.len == 0) sub_path else p: {79 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
83 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{80 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
84 p.sub_path, sub_path,81 p.sub_path, sub_path,
85 }) catch return error.NameTooLong;82 }) catch return error.NameTooLong;
86 };83 };
87 return p.root_dir.handle.openDir(joined_path, args);84 return p.root_dir.handle.openDir(io, joined_path, args);
88}85}
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 {
91 var buf: [fs.max_path_bytes]u8 = undefined;88 var buf: [fs.max_path_bytes]u8 = undefined;
92 const joined_path = if (p.sub_path.len == 0) sub_path else p: {89 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
93 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{90 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
94 p.sub_path, sub_path,91 p.sub_path, sub_path,
95 }) catch return error.NameTooLong;92 }) catch return error.NameTooLong;
96 };93 };
97 return p.root_dir.handle.makeOpenPath(joined_path, opts);94 return p.root_dir.handle.createDirPathOpen(io, joined_path, opts);
98}95}
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 {
101 var buf: [fs.max_path_bytes]u8 = undefined;98 var buf: [fs.max_path_bytes]u8 = undefined;
102 const joined_path = if (p.sub_path.len == 0) sub_path else p: {99 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
103 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{100 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
104 p.sub_path, sub_path,101 p.sub_path, sub_path,
105 }) catch return error.NameTooLong;102 }) catch return error.NameTooLong;
106 };103 };
107 return p.root_dir.handle.statFile(joined_path);104 return p.root_dir.handle.statFile(io, joined_path, .{});
108}105}
109106
110pub fn atomicFile(107pub fn atomicFile(
111 p: Path,108 p: Path,
109 io: Io,
112 sub_path: []const u8,110 sub_path: []const u8,
113 options: fs.Dir.AtomicFileOptions,111 options: Io.Dir.AtomicFileOptions,
114 buf: *[fs.max_path_bytes]u8,112 buf: *[fs.max_path_bytes]u8,
115) !fs.AtomicFile {113) !fs.AtomicFile {
116 const joined_path = if (p.sub_path.len == 0) sub_path else p: {114 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
...@@ -118,27 +116,27 @@ pub fn atomicFile(...@@ -118,27 +116,27 @@ pub fn atomicFile(
118 p.sub_path, sub_path,116 p.sub_path, sub_path,
119 }) catch return error.NameTooLong;117 }) catch return error.NameTooLong;
120 };118 };
121 return p.root_dir.handle.atomicFile(joined_path, options);119 return p.root_dir.handle.atomicFile(io, joined_path, options);
122}120}
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 {
125 var buf: [fs.max_path_bytes]u8 = undefined;123 var buf: [fs.max_path_bytes]u8 = undefined;
126 const joined_path = if (p.sub_path.len == 0) sub_path else p: {124 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
127 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{125 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
128 p.sub_path, sub_path,126 p.sub_path, sub_path,
129 }) catch return error.NameTooLong;127 }) catch return error.NameTooLong;
130 };128 };
131 return p.root_dir.handle.access(joined_path, flags);129 return p.root_dir.handle.access(io, joined_path, flags);
132}130}
133131
134pub fn makePath(p: Path, sub_path: []const u8) !void {132pub fn createDirPath(p: Path, io: Io, sub_path: []const u8) !void {
135 var buf: [fs.max_path_bytes]u8 = undefined;133 var buf: [fs.max_path_bytes]u8 = undefined;
136 const joined_path = if (p.sub_path.len == 0) sub_path else p: {134 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
137 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{135 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
138 p.sub_path, sub_path,136 p.sub_path, sub_path,
139 }) catch return error.NameTooLong;137 }) catch return error.NameTooLong;
140 };138 };
141 return p.root_dir.handle.makePath(joined_path);139 return p.root_dir.handle.createDirPath(io, joined_path);
142}140}
143141
144pub fn toString(p: Path, allocator: Allocator) Allocator.Error![]u8 {142pub 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 {...@@ -180,7 +178,7 @@ pub fn formatEscapeChar(path: Path, writer: *Io.Writer) Io.Writer.Error!void {
180}178}
181179
182pub fn format(self: Path, writer: *Io.Writer) Io.Writer.Error!void {180pub 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)) {
184 try writer.writeAll(self.sub_path);182 try writer.writeAll(self.sub_path);
185 return;183 return;
186 }184 }
...@@ -225,9 +223,9 @@ pub const TableAdapter = struct {...@@ -225,9 +223,9 @@ pub const TableAdapter = struct {
225223
226 pub fn hash(self: TableAdapter, a: Cache.Path) u32 {224 pub fn hash(self: TableAdapter, a: Cache.Path) u32 {
227 _ = self;225 _ = self;
228 const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.fd))) {226 const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.handle))) {
229 .pointer => @intFromPtr(a.root_dir.handle.fd),227 .pointer => @intFromPtr(a.root_dir.handle.handle),
230 .int => @as(u32, @bitCast(a.root_dir.handle.fd)),228 .int => @as(u32, @bitCast(a.root_dir.handle.handle)),
231 else => @compileError("unimplemented hash function"),229 else => @compileError("unimplemented hash function"),
232 };230 };
233 return @truncate(Hash.hash(seed, a.sub_path));231 return @truncate(Hash.hash(seed, a.sub_path));
lib/std/Build/Fuzz.zig+35-34
...@@ -9,14 +9,12 @@ const Allocator = std.mem.Allocator;...@@ -9,14 +9,12 @@ const Allocator = std.mem.Allocator;
9const log = std.log;9const log = std.log;
10const Coverage = std.debug.Coverage;10const Coverage = std.debug.Coverage;
11const abi = Build.abi.fuzz;11const abi = Build.abi.fuzz;
12const tty = std.Io.tty;
1312
14const Fuzz = @This();13const Fuzz = @This();
15const build_runner = @import("root");14const build_runner = @import("root");
1615
17gpa: Allocator,16gpa: Allocator,
18io: Io,17io: Io,
19ttyconf: tty.Config,
20mode: Mode,18mode: Mode,
2119
22/// Allocated into `gpa`.20/// Allocated into `gpa`.
...@@ -77,7 +75,6 @@ const CoverageMap = struct {...@@ -77,7 +75,6 @@ const CoverageMap = struct {
77pub fn init(75pub fn init(
78 gpa: Allocator,76 gpa: Allocator,
79 io: Io,77 io: Io,
80 ttyconf: tty.Config,
81 all_steps: []const *Build.Step,78 all_steps: []const *Build.Step,
82 root_prog_node: std.Progress.Node,79 root_prog_node: std.Progress.Node,
83 mode: Mode,80 mode: Mode,
...@@ -95,7 +92,7 @@ pub fn init(...@@ -95,7 +92,7 @@ pub fn init(
95 if (run.producer == null) continue;92 if (run.producer == null) continue;
96 if (run.fuzz_tests.items.len == 0) continue;93 if (run.fuzz_tests.items.len == 0) continue;
97 try steps.append(gpa, run);94 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 });
99 }96 }
10097
101 if (steps.items.len == 0) fatal("no fuzz tests found", .{});98 if (steps.items.len == 0) fatal("no fuzz tests found", .{});
...@@ -115,7 +112,6 @@ pub fn init(...@@ -115,7 +112,6 @@ pub fn init(
115 return .{112 return .{
116 .gpa = gpa,113 .gpa = gpa,
117 .io = io,114 .io = io,
118 .ttyconf = ttyconf,
119 .mode = mode,115 .mode = mode,
120 .run_steps = run_steps,116 .run_steps = run_steps,
121 .group = .init,117 .group = .init,
...@@ -154,14 +150,16 @@ pub fn deinit(fuzz: *Fuzz) void {...@@ -154,14 +150,16 @@ pub fn deinit(fuzz: *Fuzz) void {
154 fuzz.gpa.free(fuzz.run_steps);150 fuzz.gpa.free(fuzz.run_steps);
155}151}
156152
157fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, ttyconf: tty.Config, parent_prog_node: std.Progress.Node) void {153fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) void {
158 rebuildTestsWorkerRunFallible(run, gpa, ttyconf, parent_prog_node) catch |err| {154 rebuildTestsWorkerRunFallible(run, gpa, parent_prog_node) catch |err| {
159 const compile = run.producer.?;155 const compile = run.producer.?;
160 log.err("step '{s}': failed to rebuild in fuzz mode: {t}", .{ compile.step.name, err });156 log.err("step '{s}': failed to rebuild in fuzz mode: {t}", .{ compile.step.name, err });
161 };157 };
162}158}
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;
165 const compile = run.producer.?;163 const compile = run.producer.?;
166 const prog_node = parent_prog_node.start(compile.step.name, 0);164 const prog_node = parent_prog_node.start(compile.step.name, 0);
167 defer prog_node.end();165 defer prog_node.end();
...@@ -174,9 +172,9 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Co...@@ -174,9 +172,9 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Co
174172
175 if (show_error_msgs or show_compile_errors or show_stderr) {173 if (show_error_msgs or show_compile_errors or show_stderr) {
176 var buf: [256]u8 = undefined;174 var buf: [256]u8 = undefined;
177 const w, _ = std.debug.lockStderrWriter(&buf);175 const stderr = try io.lockStderr(&buf, graph.stderr_mode);
178 defer std.debug.unlockStderrWriter();176 defer io.unlockStderr();
179 build_runner.printErrorMessages(gpa, &compile.step, .{}, w, ttyconf, .verbose, .indent) catch {};177 build_runner.printErrorMessages(gpa, &compile.step, .{}, stderr.terminal(), .verbose, .indent) catch {};
180 }178 }
181179
182 const rebuilt_bin_path = result catch |err| switch (err) {180 const rebuilt_bin_path = result catch |err| switch (err) {
...@@ -186,12 +184,11 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Co...@@ -186,12 +184,11 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, ttyconf: tty.Co
186 run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename);184 run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename);
187}185}
188186
189fn fuzzWorkerRun(187fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_index: u32) void {
190 fuzz: *Fuzz,188 const owner = run.step.owner;
191 run: *Step.Run,189 const gpa = owner.allocator;
192 unit_test_index: u32,190 const graph = owner.graph;
193) void {191 const io = graph.io;
194 const gpa = run.step.owner.allocator;
195 const test_name = run.cached_test_metadata.?.testName(unit_test_index);192 const test_name = run.cached_test_metadata.?.testName(unit_test_index);
196193
197 const prog_node = fuzz.prog_node.start(test_name, 0);194 const prog_node = fuzz.prog_node.start(test_name, 0);
...@@ -200,9 +197,11 @@ fn fuzzWorkerRun(...@@ -200,9 +197,11 @@ fn fuzzWorkerRun(
200 run.rerunInFuzzMode(fuzz, unit_test_index, prog_node) catch |err| switch (err) {197 run.rerunInFuzzMode(fuzz, unit_test_index, prog_node) catch |err| switch (err) {
201 error.MakeFailed => {198 error.MakeFailed => {
202 var buf: [256]u8 = undefined;199 var buf: [256]u8 = undefined;
203 const w, _ = std.debug.lockStderrWriter(&buf);200 const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) {
204 defer std.debug.unlockStderrWriter();201 error.Canceled => return,
205 build_runner.printErrorMessages(gpa, &run.step, .{}, w, fuzz.ttyconf, .verbose, .indent) catch {};202 };
203 defer io.unlockStderr();
204 build_runner.printErrorMessages(gpa, &run.step, .{}, stderr.terminal(), .verbose, .indent) catch {};
206 return;205 return;
207 },206 },
208 else => {207 else => {
...@@ -360,12 +359,13 @@ fn coverageRunCancelable(fuzz: *Fuzz) Io.Cancelable!void {...@@ -360,12 +359,13 @@ fn coverageRunCancelable(fuzz: *Fuzz) Io.Cancelable!void {
360fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutOfMemory, AlreadyReported, Canceled }!void {359fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutOfMemory, AlreadyReported, Canceled }!void {
361 assert(fuzz.mode == .forever);360 assert(fuzz.mode == .forever);
362 const ws = fuzz.mode.forever.ws;361 const ws = fuzz.mode.forever.ws;
362 const gpa = fuzz.gpa;
363 const io = fuzz.io;363 const io = fuzz.io;
364364
365 try fuzz.coverage_mutex.lock(io);365 try fuzz.coverage_mutex.lock(io);
366 defer fuzz.coverage_mutex.unlock(io);366 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);
369 if (gop.found_existing) {369 if (gop.found_existing) {
370 // We are fuzzing the same executable with multiple threads.370 // We are fuzzing the same executable with multiple threads.
371 // Perhaps the same unit test; perhaps a different one. In any371 // 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...@@ -383,12 +383,13 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
383 .entry_points = .{},383 .entry_points = .{},
384 .start_timestamp = ws.now(),384 .start_timestamp = ws.now(),
385 };385 };
386 errdefer gop.value_ptr.coverage.deinit(fuzz.gpa);386 errdefer gop.value_ptr.coverage.deinit(gpa);
387387
388 const rebuilt_exe_path = run_step.rebuilt_executable.?;388 const rebuilt_exe_path = run_step.rebuilt_executable.?;
389 const target = run_step.producer.?.rootModuleTarget();389 const target = run_step.producer.?.rootModuleTarget();
390 var debug_info = std.debug.Info.load(390 var debug_info = std.debug.Info.load(
391 fuzz.gpa,391 gpa,
392 io,
392 rebuilt_exe_path,393 rebuilt_exe_path,
393 &gop.value_ptr.coverage,394 &gop.value_ptr.coverage,
394 target.ofmt,395 target.ofmt,
...@@ -399,21 +400,21 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO...@@ -399,21 +400,21 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
399 });400 });
400 return error.AlreadyReported;401 return error.AlreadyReported;
401 };402 };
402 defer debug_info.deinit(fuzz.gpa);403 defer debug_info.deinit(gpa);
403404
404 const coverage_file_path: Build.Cache.Path = .{405 const coverage_file_path: Build.Cache.Path = .{
405 .root_dir = run_step.step.owner.cache_root,406 .root_dir = run_step.step.owner.cache_root,
406 .sub_path = "v/" ++ std.fmt.hex(coverage_id),407 .sub_path = "v/" ++ std.fmt.hex(coverage_id),
407 };408 };
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| {
409 log.err("step '{s}': failed to load coverage file '{f}': {t}", .{410 log.err("step '{s}': failed to load coverage file '{f}': {t}", .{
410 run_step.step.name, coverage_file_path, err,411 run_step.step.name, coverage_file_path, err,
411 });412 });
412 return error.AlreadyReported;413 return error.AlreadyReported;
413 };414 };
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| {
417 log.err("unable to check len of coverage file '{f}': {t}", .{ coverage_file_path, err });418 log.err("unable to check len of coverage file '{f}': {t}", .{ coverage_file_path, err });
418 return error.AlreadyReported;419 return error.AlreadyReported;
419 };420 };
...@@ -433,14 +434,14 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO...@@ -433,14 +434,14 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
433434
434 const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);435 const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
435 const pcs = header.pcAddrs();436 const pcs = header.pcAddrs();
436 const source_locations = try fuzz.gpa.alloc(Coverage.SourceLocation, pcs.len);437 const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len);
437 errdefer fuzz.gpa.free(source_locations);438 errdefer gpa.free(source_locations);
438439
439 // Unfortunately the PCs array that LLVM gives us from the 8-bit PC440 // Unfortunately the PCs array that LLVM gives us from the 8-bit PC
440 // counters feature is not sorted.441 // counters feature is not sorted.
441 var sorted_pcs: std.MultiArrayList(struct { pc: u64, index: u32, sl: Coverage.SourceLocation }) = .{};442 var sorted_pcs: std.MultiArrayList(struct { pc: u64, index: u32, sl: Coverage.SourceLocation }) = .{};
442 defer sorted_pcs.deinit(fuzz.gpa);443 defer sorted_pcs.deinit(gpa);
443 try sorted_pcs.resize(fuzz.gpa, pcs.len);444 try sorted_pcs.resize(gpa, pcs.len);
444 @memcpy(sorted_pcs.items(.pc), pcs);445 @memcpy(sorted_pcs.items(.pc), pcs);
445 for (sorted_pcs.items(.index), 0..) |*v, i| v.* = @intCast(i);446 for (sorted_pcs.items(.index), 0..) |*v, i| v.* = @intCast(i);
446 sorted_pcs.sortUnstable(struct {447 sorted_pcs.sortUnstable(struct {
...@@ -451,7 +452,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO...@@ -451,7 +452,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
451 }452 }
452 }{ .addrs = sorted_pcs.items(.pc) });453 }{ .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| {
455 log.err("failed to resolve addresses to source locations: {t}", .{err});456 log.err("failed to resolve addresses to source locations: {t}", .{err});
456 return error.AlreadyReported;457 return error.AlreadyReported;
457 };458 };
...@@ -528,12 +529,12 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {...@@ -528,12 +529,12 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {
528 .root_dir = cov.run.step.owner.cache_root,529 .root_dir = cov.run.step.owner.cache_root,
529 .sub_path = "v/" ++ std.fmt.hex(cov.id),530 .sub_path = "v/" ++ std.fmt.hex(cov.id),
530 };531 };
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| {
532 fatal("step '{s}': failed to load coverage file '{f}': {t}", .{533 fatal("step '{s}': failed to load coverage file '{f}': {t}", .{
533 cov.run.step.name, coverage_file_path, err,534 cov.run.step.name, coverage_file_path, err,
534 });535 });
535 };536 };
536 defer coverage_file.close();537 defer coverage_file.close(io);
537538
538 const fuzz_abi = std.Build.abi.fuzz;539 const fuzz_abi = std.Build.abi.fuzz;
539 var rbuf: [0x1000]u8 = undefined;540 var rbuf: [0x1000]u8 = undefined;
lib/std/Build/Step.zig+29-29
...@@ -117,7 +117,6 @@ pub const MakeOptions = struct {...@@ -117,7 +117,6 @@ pub const MakeOptions = struct {
117 // it currently breaks because `std.net.Address` doesn't work there. Work around for now.117 // it currently breaks because `std.net.Address` doesn't work there. Work around for now.
118 .wasm32 => void,118 .wasm32 => void,
119 },119 },
120 ttyconf: std.Io.tty.Config,
121 /// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds.120 /// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds.
122 unit_test_timeout_ns: ?u64,121 unit_test_timeout_ns: ?u64,
123 /// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`.122 /// 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 {...@@ -329,16 +328,17 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
329}328}
330329
331/// For debugging purposes, prints identifying information about this Step.330/// 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;
333 if (step.debug_stack_trace.instruction_addresses.len > 0) {333 if (step.debug_stack_trace.instruction_addresses.len > 0) {
334 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};334 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 {};
336 } else {336 } else {
337 const field = "debug_stack_frames_count";337 const field = "debug_stack_frames_count";
338 comptime assert(@hasField(Build, field));338 comptime assert(@hasField(Build, field));
339 tty_config.setColor(w, .yellow) catch {};339 t.setColor(.yellow) catch {};
340 w.print("name: '{s}'. no stack trace collected for this step, see std.Build." ++ field ++ "\n", .{step.name}) catch {};340 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 {};
342 }342 }
343}343}
344344
...@@ -350,6 +350,7 @@ pub fn captureChildProcess(...@@ -350,6 +350,7 @@ pub fn captureChildProcess(
350 argv: []const []const u8,350 argv: []const []const u8,
351) !std.process.Child.RunResult {351) !std.process.Child.RunResult {
352 const arena = s.owner.allocator;352 const arena = s.owner.allocator;
353 const io = s.owner.graph.io;
353354
354 // If an error occurs, it's happened in this command:355 // If an error occurs, it's happened in this command:
355 assert(s.result_failed_command == null);356 assert(s.result_failed_command == null);
...@@ -358,8 +359,7 @@ pub fn captureChildProcess(...@@ -358,8 +359,7 @@ pub fn captureChildProcess(
358 try handleChildProcUnsupported(s);359 try handleChildProcUnsupported(s);
359 try handleVerbose(s.owner, null, argv);360 try handleVerbose(s.owner, null, argv);
360361
361 const result = std.process.Child.run(.{362 const result = std.process.Child.run(arena, io, .{
362 .allocator = arena,
363 .argv = argv,363 .argv = argv,
364 .progress_node = progress_node,364 .progress_node = progress_node,
365 }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err });365 }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err });
...@@ -401,6 +401,9 @@ pub fn evalZigProcess(...@@ -401,6 +401,9 @@ pub fn evalZigProcess(
401 web_server: ?*Build.WebServer,401 web_server: ?*Build.WebServer,
402 gpa: Allocator,402 gpa: Allocator,
403) !?Path {403) !?Path {
404 const b = s.owner;
405 const io = b.graph.io;
406
404 // If an error occurs, it's happened in this command:407 // If an error occurs, it's happened in this command:
405 assert(s.result_failed_command == null);408 assert(s.result_failed_command == null);
406 s.result_failed_command = try allocPrintCmd(gpa, null, argv);409 s.result_failed_command = try allocPrintCmd(gpa, null, argv);
...@@ -411,7 +414,7 @@ pub fn evalZigProcess(...@@ -411,7 +414,7 @@ pub fn evalZigProcess(
411 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {414 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
412 error.BrokenPipe => {415 error.BrokenPipe => {
413 // Process restart required.416 // Process restart required.
414 const term = zp.child.wait() catch |e| {417 const term = zp.child.wait(io) catch |e| {
415 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });418 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
416 };419 };
417 _ = term;420 _ = term;
...@@ -427,7 +430,7 @@ pub fn evalZigProcess(...@@ -427,7 +430,7 @@ pub fn evalZigProcess(
427430
428 if (s.result_error_msgs.items.len > 0 and result == null) {431 if (s.result_error_msgs.items.len > 0 and result == null) {
429 // Crash detected.432 // Crash detected.
430 const term = zp.child.wait() catch |e| {433 const term = zp.child.wait(io) catch |e| {
431 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });434 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
432 };435 };
433 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;436 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
...@@ -439,7 +442,6 @@ pub fn evalZigProcess(...@@ -439,7 +442,6 @@ pub fn evalZigProcess(
439 return result;442 return result;
440 }443 }
441 assert(argv.len != 0);444 assert(argv.len != 0);
442 const b = s.owner;
443 const arena = b.allocator;445 const arena = b.allocator;
444446
445 try handleChildProcUnsupported(s);447 try handleChildProcUnsupported(s);
...@@ -453,7 +455,7 @@ pub fn evalZigProcess(...@@ -453,7 +455,7 @@ pub fn evalZigProcess(
453 child.request_resource_usage_statistics = true;455 child.request_resource_usage_statistics = true;
454 child.progress_node = prog_node;456 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
458 const zp = try gpa.create(ZigProcess);460 const zp = try gpa.create(ZigProcess);
459 zp.* = .{461 zp.* = .{
...@@ -474,10 +476,10 @@ pub fn evalZigProcess(...@@ -474,10 +476,10 @@ pub fn evalZigProcess(
474476
475 if (!watch) {477 if (!watch) {
476 // Send EOF to stdin.478 // Send EOF to stdin.
477 zp.child.stdin.?.close();479 zp.child.stdin.?.close(io);
478 zp.child.stdin = null;480 zp.child.stdin = null;
479481
480 const term = zp.child.wait() catch |err| {482 const term = zp.child.wait(io) catch |err| {
481 return s.fail("unable to wait for {s}: {t}", .{ argv[0], err });483 return s.fail("unable to wait for {s}: {t}", .{ argv[0], err });
482 };484 };
483 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;485 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
...@@ -504,36 +506,34 @@ pub fn evalZigProcess(...@@ -504,36 +506,34 @@ pub fn evalZigProcess(
504 return result;506 return result;
505}507}
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.
508pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus {510pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus {
509 const b = s.owner;511 const b = s.owner;
510 const io = b.graph.io;512 const io = b.graph.io;
511 const src_path = src_lazy_path.getPath3(b, s);513 const src_path = src_lazy_path.getPath3(b, s);
512 try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });514 try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });
513 return Io.Dir.updateFile(src_path.root_dir.handle.adaptToNewApi(), io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| {515 return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err|
514 return s.fail("unable to update file from '{f}' to '{s}': {t}", .{516 return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err });
515 src_path, dest_path, err,
516 });
517 };
518}517}
519518
520/// Wrapper around `std.fs.Dir.makePathStatus` that handles verbose and error output.519/// Wrapper around `Io.Dir.createDirPathStatus` that handles verbose and error output.
521pub fn installDir(s: *Step, dest_path: []const u8) !std.fs.Dir.MakePathStatus {520pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.CreatePathStatus {
522 const b = s.owner;521 const b = s.owner;
522 const io = b.graph.io;
523 try handleVerbose(b, null, &.{ "install", "-d", dest_path });523 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|
525 return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err });525 return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err });
526 };
527}526}
528527
529fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.WebServer, gpa: Allocator) !?Path {528fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.WebServer, gpa: Allocator) !?Path {
530 const b = s.owner;529 const b = s.owner;
531 const arena = b.allocator;530 const arena = b.allocator;
531 const io = b.graph.io;
532532
533 var timer = try std.time.Timer.start();533 var timer = try std.time.Timer.start();
534534
535 try sendMessage(zp.child.stdin.?, .update);535 try sendMessage(io, zp.child.stdin.?, .update);
536 if (!watch) try sendMessage(zp.child.stdin.?, .exit);536 if (!watch) try sendMessage(io, zp.child.stdin.?, .exit);
537537
538 var result: ?Path = null;538 var result: ?Path = null;
539539
...@@ -670,12 +670,12 @@ fn clearZigProcess(s: *Step, gpa: Allocator) void {...@@ -670,12 +670,12 @@ fn clearZigProcess(s: *Step, gpa: Allocator) void {
670 }670 }
671}671}
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 {
674 const header: std.zig.Client.Message.Header = .{674 const header: std.zig.Client.Message.Header = .{
675 .tag = tag,675 .tag = tag,
676 .bytes_len = 0,676 .bytes_len = 0,
677 };677 };
678 var w = file.writer(&.{});678 var w = file.writer(io, &.{});
679 w.interface.writeStruct(header, .little) catch |err| switch (err) {679 w.interface.writeStruct(header, .little) catch |err| switch (err) {
680 error.WriteFailed => return w.err.?,680 error.WriteFailed => return w.err.?,
681 };681 };
...@@ -898,7 +898,7 @@ pub fn addWatchInput(step: *Step, lazy_file: Build.LazyPath) Allocator.Error!voi...@@ -898,7 +898,7 @@ pub fn addWatchInput(step: *Step, lazy_file: Build.LazyPath) Allocator.Error!voi
898 try addWatchInputFromPath(step, .{898 try addWatchInputFromPath(step, .{
899 .root_dir = .{899 .root_dir = .{
900 .path = null,900 .path = null,
901 .handle = std.fs.cwd(),901 .handle = Io.Dir.cwd(),
902 },902 },
903 .sub_path = std.fs.path.dirname(path_string) orelse "",903 .sub_path = std.fs.path.dirname(path_string) orelse "",
904 }, std.fs.path.basename(path_string));904 }, std.fs.path.basename(path_string));
...@@ -923,7 +923,7 @@ pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Alloc...@@ -923,7 +923,7 @@ pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Alloc
923 try addDirectoryWatchInputFromPath(step, .{923 try addDirectoryWatchInputFromPath(step, .{
924 .root_dir = .{924 .root_dir = .{
925 .path = null,925 .path = null,
926 .handle = std.fs.cwd(),926 .handle = Io.Dir.cwd(),
927 },927 },
928 .sub_path = path_string,928 .sub_path = path_string,
929 });929 });
lib/std/Build/Step/CheckFile.zig+4-1
...@@ -3,7 +3,9 @@...@@ -3,7 +3,9 @@
3//! TODO: generalize the code in std.testing.expectEqualStrings and make this3//! TODO: generalize the code in std.testing.expectEqualStrings and make this
4//! CheckFile step produce those helpful diagnostics when there is not a match.4//! CheckFile step produce those helpful diagnostics when there is not a match.
5const CheckFile = @This();5const CheckFile = @This();
6
6const std = @import("std");7const std = @import("std");
8const Io = std.Io;
7const Step = std.Build.Step;9const Step = std.Build.Step;
8const fs = std.fs;10const fs = std.fs;
9const mem = std.mem;11const mem = std.mem;
...@@ -49,11 +51,12 @@ pub fn setName(check_file: *CheckFile, name: []const u8) void {...@@ -49,11 +51,12 @@ pub fn setName(check_file: *CheckFile, name: []const u8) void {
49fn make(step: *Step, options: Step.MakeOptions) !void {51fn make(step: *Step, options: Step.MakeOptions) !void {
50 _ = options;52 _ = options;
51 const b = step.owner;53 const b = step.owner;
54 const io = b.graph.io;
52 const check_file: *CheckFile = @fieldParentPtr("step", step);55 const check_file: *CheckFile = @fieldParentPtr("step", step);
53 try step.singleUnchangingWatchInput(check_file.source);56 try step.singleUnchangingWatchInput(check_file.source);
5457
55 const src_path = check_file.source.getPath2(b, step);58 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| {
57 return step.fail("unable to read '{s}': {s}", .{60 return step.fail("unable to read '{s}': {s}", .{
58 src_path, @errorName(err),61 src_path, @errorName(err),
59 });62 });
lib/std/Build/Step/CheckObject.zig+2
...@@ -547,12 +547,14 @@ pub fn checkComputeCompare(...@@ -547,12 +547,14 @@ pub fn checkComputeCompare(
547fn make(step: *Step, make_options: Step.MakeOptions) !void {547fn make(step: *Step, make_options: Step.MakeOptions) !void {
548 _ = make_options;548 _ = make_options;
549 const b = step.owner;549 const b = step.owner;
550 const io = b.graph.io;
550 const gpa = b.allocator;551 const gpa = b.allocator;
551 const check_object: *CheckObject = @fieldParentPtr("step", step);552 const check_object: *CheckObject = @fieldParentPtr("step", step);
552 try step.singleUnchangingWatchInput(check_object.source);553 try step.singleUnchangingWatchInput(check_object.source);
553554
554 const src_path = check_object.source.getPath3(b, step);555 const src_path = check_object.source.getPath3(b, step);
555 const contents = src_path.root_dir.handle.readFileAllocOptions(556 const contents = src_path.root_dir.handle.readFileAllocOptions(
557 io,
556 src_path.sub_path,558 src_path.sub_path,
557 gpa,559 gpa,
558 .limited(check_object.max_bytes),560 .limited(check_object.max_bytes),
lib/std/Build/Step/Compile.zig+35-24
...@@ -1,12 +1,15 @@...@@ -1,12 +1,15 @@
1const Compile = @This();
1const builtin = @import("builtin");2const builtin = @import("builtin");
3
2const std = @import("std");4const std = @import("std");
5const Io = std.Io;
3const mem = std.mem;6const mem = std.mem;
4const fs = std.fs;7const fs = std.fs;
5const assert = std.debug.assert;8const assert = std.debug.assert;
6const panic = std.debug.panic;9const panic = std.debug.panic;
7const StringHashMap = std.StringHashMap;10const StringHashMap = std.StringHashMap;
8const Sha256 = std.crypto.hash.sha2.Sha256;11const Sha256 = std.crypto.hash.sha2.Sha256;
9const Allocator = mem.Allocator;12const Allocator = std.mem.Allocator;
10const Step = std.Build.Step;13const Step = std.Build.Step;
11const LazyPath = std.Build.LazyPath;14const LazyPath = std.Build.LazyPath;
12const PkgConfigPkg = std.Build.PkgConfigPkg;15const PkgConfigPkg = std.Build.PkgConfigPkg;
...@@ -15,7 +18,6 @@ const RunError = std.Build.RunError;...@@ -15,7 +18,6 @@ const RunError = std.Build.RunError;
15const Module = std.Build.Module;18const Module = std.Build.Module;
16const InstallDir = std.Build.InstallDir;19const InstallDir = std.Build.InstallDir;
17const GeneratedFile = std.Build.GeneratedFile;20const GeneratedFile = std.Build.GeneratedFile;
18const Compile = @This();
19const Path = std.Build.Cache.Path;21const Path = std.Build.Cache.Path;
2022
21pub const base_id: Step.Id = .compile;23pub const base_id: Step.Id = .compile;
...@@ -920,20 +922,24 @@ const CliNamedModules = struct {...@@ -920,20 +922,24 @@ const CliNamedModules = struct {
920 }922 }
921};923};
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;
924 const maybe_path: ?*GeneratedFile = @field(compile, tag_name);930 const maybe_path: ?*GeneratedFile = @field(compile, tag_name);
925931
926 const generated_file = maybe_path orelse {932 const generated_file = maybe_path orelse {
927 const w, const ttyconf = std.debug.lockStderrWriter(&.{});933 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
928 std.Build.dumpBadGetPathHelp(&compile.step, w, ttyconf, compile.step.owner, asking_step) catch {};934 std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {};
929 std.debug.unlockStderrWriter();935 io.unlockStderr();
930 @panic("missing emit option for " ++ tag_name);936 @panic("missing emit option for " ++ tag_name);
931 };937 };
932938
933 const path = generated_file.path orelse {939 const path = generated_file.path orelse {
934 const w, const ttyconf = std.debug.lockStderrWriter(&.{});940 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
935 std.Build.dumpBadGetPathHelp(&compile.step, w, ttyconf, compile.step.owner, asking_step) catch {};941 std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {};
936 std.debug.unlockStderrWriter();942 io.unlockStderr();
937 @panic(tag_name ++ " is null. Is there a missing step dependency?");943 @panic(tag_name ++ " is null. Is there a missing step dependency?");
938 };944 };
939945
...@@ -1147,9 +1153,9 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {...@@ -1147,9 +1153,9 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1147 // For everything else, we directly link1153 // For everything else, we directly link
1148 // against the library file.1154 // against the library file.
1149 const full_path_lib = if (other_produces_implib)1155 const full_path_lib = if (other_produces_implib)
1150 other.getGeneratedFilePath("generated_implib", &compile.step)1156 try other.getGeneratedFilePath("generated_implib", &compile.step)
1151 else1157 else
1152 other.getGeneratedFilePath("generated_bin", &compile.step);1158 try other.getGeneratedFilePath("generated_bin", &compile.step);
11531159
1154 try zig_args.append(full_path_lib);1160 try zig_args.append(full_path_lib);
1155 total_linker_objects += 1;1161 total_linker_objects += 1;
...@@ -1561,19 +1567,22 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {...@@ -1561,19 +1567,22 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1561 }1567 }
15621568
1563 // -I and -L arguments that appear after the last --mod argument apply to all modules.1569 // -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
1564 for (b.search_prefixes.items) |search_prefix| {1573 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| {
1566 return step.fail("unable to open prefix directory '{s}': {s}", .{1575 return step.fail("unable to open prefix directory '{s}': {s}", .{
1567 search_prefix, @errorName(err),1576 search_prefix, @errorName(err),
1568 });1577 });
1569 };1578 };
1570 defer prefix_dir.close();1579 defer prefix_dir.close(io);
15711580
1572 // Avoid passing -L and -I flags for nonexistent directories.1581 // Avoid passing -L and -I flags for nonexistent directories.
1573 // This prevents a warning, that should probably be upgraded to an error in Zig's1582 // This prevents a warning, that should probably be upgraded to an error in Zig's
1574 // CLI parsing code, when the linker sees an -L directory that does not exist.1583 // 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", .{})) |_| {
1577 try zig_args.appendSlice(&.{1586 try zig_args.appendSlice(&.{
1578 "-L", b.pathJoin(&.{ search_prefix, "lib" }),1587 "-L", b.pathJoin(&.{ search_prefix, "lib" }),
1579 });1588 });
...@@ -1584,7 +1593,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {...@@ -1584,7 +1593,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1584 }),1593 }),
1585 }1594 }
15861595
1587 if (prefix_dir.access("include", .{})) |_| {1596 if (prefix_dir.access(io, "include", .{})) |_| {
1588 try zig_args.appendSlice(&.{1597 try zig_args.appendSlice(&.{
1589 "-I", b.pathJoin(&.{ search_prefix, "include" }),1598 "-I", b.pathJoin(&.{ search_prefix, "include" }),
1590 });1599 });
...@@ -1660,7 +1669,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {...@@ -1660,7 +1669,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1660 args_length += arg.len + 1; // +1 to account for null terminator1669 args_length += arg.len + 1; // +1 to account for null terminator
1661 }1670 }
1662 if (args_length >= 30 * 1024) {1671 if (args_length >= 30 * 1024) {
1663 try b.cache_root.handle.makePath("args");1672 try b.cache_root.handle.createDirPath(io, "args");
16641673
1665 const args_to_escape = zig_args.items[2..];1674 const args_to_escape = zig_args.items[2..];
1666 var escaped_args = try std.array_list.Managed([]const u8).initCapacity(arena, args_to_escape.len);1675 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 {...@@ -1693,18 +1702,18 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1693 _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash});1702 _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash});
16941703
1695 const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash;1704 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, .{})) |_| {
1697 // The args file is already present from a previous run.1706 // The args file is already present from a previous run.
1698 } else |err| switch (err) {1707 } else |err| switch (err) {
1699 error.FileNotFound => {1708 error.FileNotFound => {
1700 try b.cache_root.handle.makePath("tmp");1709 try b.cache_root.handle.createDirPath(io, "tmp");
1701 const rand_int = std.crypto.random.int(u64);1710 const rand_int = std.crypto.random.int(u64);
1702 const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);1711 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 });1712 try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args });
1704 defer b.cache_root.handle.deleteFile(tmp_path) catch {1713 defer b.cache_root.handle.deleteFile(io, tmp_path) catch {
1705 // It's fine if the temporary file can't be cleaned up.1714 // It's fine if the temporary file can't be cleaned up.
1706 };1715 };
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) {
1708 error.PathAlreadyExists => {1717 error.PathAlreadyExists => {
1709 // The args file was created by another concurrent build process.1718 // The args file was created by another concurrent build process.
1710 },1719 },
...@@ -1816,18 +1825,20 @@ pub fn doAtomicSymLinks(...@@ -1816,18 +1825,20 @@ pub fn doAtomicSymLinks(
1816 filename_name_only: []const u8,1825 filename_name_only: []const u8,
1817) !void {1826) !void {
1818 const b = step.owner;1827 const b = step.owner;
1828 const io = b.graph.io;
1819 const out_dir = fs.path.dirname(output_path) orelse ".";1829 const out_dir = fs.path.dirname(output_path) orelse ".";
1820 const out_basename = fs.path.basename(output_path);1830 const out_basename = fs.path.basename(output_path);
1821 // sym link for libfoo.so.1 to libfoo.so.1.2.31831 // sym link for libfoo.so.1 to libfoo.so.1.2.3
1822 const major_only_path = b.pathJoin(&.{ out_dir, filename_major_only });1832 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| {
1824 return step.fail("unable to symlink {s} -> {s}: {s}", .{1835 return step.fail("unable to symlink {s} -> {s}: {s}", .{
1825 major_only_path, out_basename, @errorName(err),1836 major_only_path, out_basename, @errorName(err),
1826 });1837 });
1827 };1838 };
1828 // sym link for libfoo.so to libfoo.so.11839 // sym link for libfoo.so to libfoo.so.1
1829 const name_only_path = b.pathJoin(&.{ out_dir, filename_name_only });1840 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| {
1831 return step.fail("Unable to symlink {s} -> {s}: {s}", .{1842 return step.fail("Unable to symlink {s} -> {s}: {s}", .{
1832 name_only_path, filename_major_only, @errorName(err),1843 name_only_path, filename_major_only, @errorName(err),
1833 });1844 });
...@@ -1897,7 +1908,7 @@ fn checkCompileErrors(compile: *Compile) !void {...@@ -1897,7 +1908,7 @@ fn checkCompileErrors(compile: *Compile) !void {
1897 try actual_eb.renderToWriter(.{1908 try actual_eb.renderToWriter(.{
1898 .include_reference_trace = false,1909 .include_reference_trace = false,
1899 .include_source_line = false,1910 .include_source_line = false,
1900 }, &aw.writer, .no_color);1911 }, &aw.writer);
1901 break :ae try aw.toOwnedSlice();1912 break :ae try aw.toOwnedSlice();
1902 };1913 };
19031914
lib/std/Build/Step/ConfigHeader.zig+8-5
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1const std = @import("std");
2const ConfigHeader = @This();1const ConfigHeader = @This();
2
3const std = @import("std");
4const Io = std.Io;
3const Step = std.Build.Step;5const Step = std.Build.Step;
4const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
5const Writer = std.Io.Writer;7const Writer = std.Io.Writer;
...@@ -182,6 +184,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -182,6 +184,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
182184
183 const gpa = b.allocator;185 const gpa = b.allocator;
184 const arena = b.allocator;186 const arena = b.allocator;
187 const io = b.graph.io;
185188
186 var man = b.graph.cache.obtain();189 var man = b.graph.cache.obtain();
187 defer man.deinit();190 defer man.deinit();
...@@ -205,7 +208,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -205,7 +208,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
205 .autoconf_undef, .autoconf_at => |file_source| {208 .autoconf_undef, .autoconf_at => |file_source| {
206 try bw.writeAll(c_generated_line);209 try bw.writeAll(c_generated_line);
207 const src_path = file_source.getPath2(b, step);210 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| {
209 return step.fail("unable to read autoconf input file '{s}': {s}", .{212 return step.fail("unable to read autoconf input file '{s}': {s}", .{
210 src_path, @errorName(err),213 src_path, @errorName(err),
211 });214 });
...@@ -219,7 +222,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -219,7 +222,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
219 .cmake => |file_source| {222 .cmake => |file_source| {
220 try bw.writeAll(c_generated_line);223 try bw.writeAll(c_generated_line);
221 const src_path = file_source.getPath2(b, step);224 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| {
223 return step.fail("unable to read cmake input file '{s}': {s}", .{226 return step.fail("unable to read cmake input file '{s}': {s}", .{
224 src_path, @errorName(err),227 src_path, @errorName(err),
225 });228 });
...@@ -255,13 +258,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -255,13 +258,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
255 const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path });258 const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path });
256 const sub_path_dirname = std.fs.path.dirname(sub_path).?;259 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| {
259 return step.fail("unable to make path '{f}{s}': {s}", .{262 return step.fail("unable to make path '{f}{s}': {s}", .{
260 b.cache_root, sub_path_dirname, @errorName(err),263 b.cache_root, sub_path_dirname, @errorName(err),
261 });264 });
262 };265 };
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| {
265 return step.fail("unable to write file '{f}{s}': {s}", .{268 return step.fail("unable to write file '{f}{s}': {s}", .{
266 b.cache_root, sub_path, @errorName(err),269 b.cache_root, sub_path, @errorName(err),
267 });270 });
lib/std/Build/Step/InstallArtifact.zig+4-3
...@@ -119,6 +119,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -119,6 +119,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
119 _ = options;119 _ = options;
120 const install_artifact: *InstallArtifact = @fieldParentPtr("step", step);120 const install_artifact: *InstallArtifact = @fieldParentPtr("step", step);
121 const b = step.owner;121 const b = step.owner;
122 const io = b.graph.io;
122123
123 var all_cached = true;124 var all_cached = true;
124125
...@@ -163,15 +164,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -163,15 +164,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
163 const src_dir_path = dir.source.getPath3(b, step);164 const src_dir_path = dir.source.getPath3(b, step);
164 const full_h_prefix = b.getInstallPath(h_dir, dir.dest_rel_path);165 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| {
167 return step.fail("unable to open source directory '{f}': {s}", .{168 return step.fail("unable to open source directory '{f}': {s}", .{
168 src_dir_path, @errorName(err),169 src_dir_path, @errorName(err),
169 });170 });
170 };171 };
171 defer src_dir.close();172 defer src_dir.close(io);
172173
173 var it = try src_dir.walk(b.allocator);174 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| {
175 for (dir.options.exclude_extensions) |ext| {176 for (dir.options.exclude_extensions) |ext| {
176 if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry;177 if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry;
177 }178 }
lib/std/Build/Step/InstallDir.zig+5-6
...@@ -58,21 +58,20 @@ pub fn create(owner: *std.Build, options: Options) *InstallDir {...@@ -58,21 +58,20 @@ pub fn create(owner: *std.Build, options: Options) *InstallDir {
58fn make(step: *Step, options: Step.MakeOptions) !void {58fn make(step: *Step, options: Step.MakeOptions) !void {
59 _ = options;59 _ = options;
60 const b = step.owner;60 const b = step.owner;
61 const io = b.graph.io;
61 const install_dir: *InstallDir = @fieldParentPtr("step", step);62 const install_dir: *InstallDir = @fieldParentPtr("step", step);
62 step.clearWatchInputs();63 step.clearWatchInputs();
63 const arena = b.allocator;64 const arena = b.allocator;
64 const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir);65 const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir);
65 const src_dir_path = install_dir.options.source_dir.getPath3(b, step);66 const src_dir_path = install_dir.options.source_dir.getPath3(b, step);
66 const need_derived_inputs = try step.addDirectoryWatchInput(install_dir.options.source_dir);67 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 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
68 return step.fail("unable to open source directory '{f}': {s}", .{69 return step.fail("unable to open source directory '{f}': {t}", .{ src_dir_path, err });
69 src_dir_path, @errorName(err),
70 });
71 };70 };
72 defer src_dir.close();71 defer src_dir.close(io);
73 var it = try src_dir.walk(arena);72 var it = try src_dir.walk(arena);
74 var all_cached = true;73 var all_cached = true;
75 next_entry: while (try it.next()) |entry| {74 next_entry: while (try it.next(io)) |entry| {
76 for (install_dir.options.exclude_extensions) |ext| {75 for (install_dir.options.exclude_extensions) |ext| {
77 if (mem.endsWith(u8, entry.path, ext)) continue :next_entry;76 if (mem.endsWith(u8, entry.path, ext)) continue :next_entry;
78 }77 }
lib/std/Build/Step/ObjCopy.zig+3-2
...@@ -3,7 +3,7 @@ const ObjCopy = @This();...@@ -3,7 +3,7 @@ const ObjCopy = @This();
33
4const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
5const ArenaAllocator = std.heap.ArenaAllocator;5const ArenaAllocator = std.heap.ArenaAllocator;
6const File = std.fs.File;6const File = std.Io.File;
7const InstallDir = std.Build.InstallDir;7const InstallDir = std.Build.InstallDir;
8const Step = std.Build.Step;8const Step = std.Build.Step;
9const elf = std.elf;9const elf = std.elf;
...@@ -143,6 +143,7 @@ pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath {...@@ -143,6 +143,7 @@ pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath {
143fn make(step: *Step, options: Step.MakeOptions) !void {143fn make(step: *Step, options: Step.MakeOptions) !void {
144 const prog_node = options.progress_node;144 const prog_node = options.progress_node;
145 const b = step.owner;145 const b = step.owner;
146 const io = b.graph.io;
146 const objcopy: *ObjCopy = @fieldParentPtr("step", step);147 const objcopy: *ObjCopy = @fieldParentPtr("step", step);
147 try step.singleUnchangingWatchInput(objcopy.input_file);148 try step.singleUnchangingWatchInput(objcopy.input_file);
148149
...@@ -176,7 +177,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -176,7 +177,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
176 const cache_path = "o" ++ fs.path.sep_str ++ digest;177 const cache_path = "o" ++ fs.path.sep_str ++ digest;
177 const full_dest_path = try b.cache_root.join(b.allocator, &.{ cache_path, objcopy.basename });178 const full_dest_path = try b.cache_root.join(b.allocator, &.{ cache_path, objcopy.basename });
178 const full_dest_path_debug = try b.cache_root.join(b.allocator, &.{ cache_path, b.fmt("{s}.debug", .{objcopy.basename}) });179 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| {
180 return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });181 return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });
181 };182 };
182183
lib/std/Build/Step/Options.zig+28-29
...@@ -1,12 +1,13 @@...@@ -1,12 +1,13 @@
1const std = @import("std");1const Options = @This();
2const builtin = @import("builtin");2const builtin = @import("builtin");
3
4const std = @import("std");
5const Io = std.Io;
3const fs = std.fs;6const fs = std.fs;
4const Step = std.Build.Step;7const Step = std.Build.Step;
5const GeneratedFile = std.Build.GeneratedFile;8const GeneratedFile = std.Build.GeneratedFile;
6const LazyPath = std.Build.LazyPath;9const LazyPath = std.Build.LazyPath;
710
8const Options = @This();
9
10pub const base_id: Step.Id = .options;11pub const base_id: Step.Id = .options;
1112
12step: Step,13step: Step,
...@@ -441,6 +442,7 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {...@@ -441,6 +442,7 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
441 _ = make_options;442 _ = make_options;
442443
443 const b = step.owner;444 const b = step.owner;
445 const io = b.graph.io;
444 const options: *Options = @fieldParentPtr("step", step);446 const options: *Options = @fieldParentPtr("step", step);
445447
446 for (options.args.items) |item| {448 for (options.args.items) |item| {
...@@ -468,18 +470,15 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {...@@ -468,18 +470,15 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
468470
469 // Optimize for the hot path. Stat the file, and if it already exists,471 // Optimize for the hot path. Stat the file, and if it already exists,
470 // cache hit.472 // cache hit.
471 if (b.cache_root.handle.access(sub_path, .{})) |_| {473 if (b.cache_root.handle.access(io, sub_path, .{})) |_| {
472 // This is the hot path, success.474 // This is the hot path, success.
473 step.result_cached = true;475 step.result_cached = true;
474 return;476 return;
475 } else |outer_err| switch (outer_err) {477 } else |outer_err| switch (outer_err) {
476 error.FileNotFound => {478 error.FileNotFound => {
477 const sub_dirname = fs.path.dirname(sub_path).?;479 const sub_dirname = fs.path.dirname(sub_path).?;
478 b.cache_root.handle.makePath(sub_dirname) catch |e| {480 b.cache_root.handle.createDirPath(io, sub_dirname) catch |e|
479 return step.fail("unable to make path '{f}{s}': {s}", .{481 return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e });
480 b.cache_root, sub_dirname, @errorName(e),
481 });
482 };
483482
484 const rand_int = std.crypto.random.int(u64);483 const rand_int = std.crypto.random.int(u64);
485 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++484 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++
...@@ -487,40 +486,40 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {...@@ -487,40 +486,40 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
487 basename;486 basename;
488 const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?;487 const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?;
489488
490 b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| {489 b.cache_root.handle.createDirPath(io, tmp_sub_path_dirname) catch |err| {
491 return step.fail("unable to make temporary directory '{f}{s}': {s}", .{490 return step.fail("unable to make temporary directory '{f}{s}': {t}", .{
492 b.cache_root, tmp_sub_path_dirname, @errorName(err),491 b.cache_root, tmp_sub_path_dirname, err,
493 });492 });
494 };493 };
495494
496 b.cache_root.handle.writeFile(.{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {495 b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
497 return step.fail("unable to write options to '{f}{s}': {s}", .{496 return step.fail("unable to write options to '{f}{s}': {t}", .{
498 b.cache_root, tmp_sub_path, @errorName(err),497 b.cache_root, tmp_sub_path, err,
499 });498 });
500 };499 };
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) {
503 error.PathAlreadyExists => {502 error.PathAlreadyExists => {
504 // Other process beat us to it. Clean up the temp file.503 // Other process beat us to it. Clean up the temp file.
505 b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| {504 b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| {
506 try step.addError("warning: unable to delete temp file '{f}{s}': {s}", .{505 try step.addError("warning: unable to delete temp file '{f}{s}': {t}", .{
507 b.cache_root, tmp_sub_path, @errorName(e),506 b.cache_root, tmp_sub_path, e,
508 });507 });
509 };508 };
510 step.result_cached = true;509 step.result_cached = true;
511 return;510 return;
512 },511 },
513 else => {512 else => {
514 return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {s}", .{513 return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {t}", .{
515 b.cache_root, tmp_sub_path,514 b.cache_root, tmp_sub_path,
516 b.cache_root, sub_path,515 b.cache_root, sub_path,
517 @errorName(err),516 err,
518 });517 });
519 },518 },
520 };519 };
521 },520 },
522 else => |e| return step.fail("unable to access options file '{f}{s}': {s}", .{521 else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{
523 b.cache_root, sub_path, @errorName(e),522 b.cache_root, sub_path, e,
524 }),523 }),
525 }524 }
526}525}
...@@ -544,11 +543,11 @@ test Options {...@@ -544,11 +543,11 @@ test Options {
544 .cache = .{543 .cache = .{
545 .io = io,544 .io = io,
546 .gpa = arena.allocator(),545 .gpa = arena.allocator(),
547 .manifest_dir = std.fs.cwd(),546 .manifest_dir = Io.Dir.cwd(),
548 },547 },
549 .zig_exe = "test",548 .zig_exe = "test",
550 .env_map = std.process.EnvMap.init(arena.allocator()),549 .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() },
552 .host = .{551 .host = .{
553 .query = .{},552 .query = .{},
554 .result = try std.zig.system.resolveTargetQuery(io, .{}),553 .result = try std.zig.system.resolveTargetQuery(io, .{}),
...@@ -559,8 +558,8 @@ test Options {...@@ -559,8 +558,8 @@ test Options {
559558
560 var builder = try std.Build.create(559 var builder = try std.Build.create(
561 &graph,560 &graph,
562 .{ .path = "test", .handle = std.fs.cwd() },561 .{ .path = "test", .handle = Io.Dir.cwd() },
563 .{ .path = "test", .handle = std.fs.cwd() },562 .{ .path = "test", .handle = Io.Dir.cwd() },
564 &.{},563 &.{},
565 );564 );
566565
lib/std/Build/Step/RemoveDir.zig+4-7
...@@ -27,6 +27,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -27,6 +27,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
27 _ = options;27 _ = options;
2828
29 const b = step.owner;29 const b = step.owner;
30 const io = b.graph.io;
30 const remove_dir: *RemoveDir = @fieldParentPtr("step", step);31 const remove_dir: *RemoveDir = @fieldParentPtr("step", step);
3132
32 step.clearWatchInputs();33 step.clearWatchInputs();
...@@ -34,15 +35,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -34,15 +35,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
3435
35 const full_doomed_path = remove_dir.doomed_path.getPath2(b, step);36 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| {
38 if (b.build_root.path) |base| {39 if (b.build_root.path) |base| {
39 return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{40 return step.fail("unable to recursively delete path '{s}/{s}': {t}", .{ base, full_doomed_path, err });
40 base, full_doomed_path, @errorName(err),
41 });
42 } else {41 } else {
43 return step.fail("unable to recursively delete path '{s}': {s}", .{42 return step.fail("unable to recursively delete path '{s}': {t}", .{ full_doomed_path, err });
44 full_doomed_path, @errorName(err),
45 });
46 }43 }
47 };44 };
48}45}
lib/std/Build/Step/Run.zig+130-120
...@@ -1,15 +1,16 @@...@@ -1,15 +1,16 @@
1const std = @import("std");1const Run = @This();
2const builtin = @import("builtin");2const builtin = @import("builtin");
3
4const std = @import("std");
5const Io = std.Io;
3const Build = std.Build;6const Build = std.Build;
4const Step = Build.Step;7const Step = std.Build.Step;
5const fs = std.fs;8const Dir = std.Io.Dir;
6const mem = std.mem;9const mem = std.mem;
7const process = std.process;10const process = std.process;
8const EnvMap = process.EnvMap;11const EnvMap = std.process.EnvMap;
9const assert = std.debug.assert;12const assert = std.debug.assert;
10const Path = Build.Cache.Path;13const Path = std.Build.Cache.Path;
11
12const Run = @This();
1314
14pub const base_id: Step.Id = .run;15pub const base_id: Step.Id = .run;
1516
...@@ -25,19 +26,7 @@ cwd: ?Build.LazyPath,...@@ -25,19 +26,7 @@ cwd: ?Build.LazyPath,
25env_map: ?*EnvMap,26env_map: ?*EnvMap,
2627
27/// Controls the `NO_COLOR` and `CLICOLOR_FORCE` environment variables.28/// Controls the `NO_COLOR` and `CLICOLOR_FORCE` environment variables.
28color: enum {29color: Color = .auto,
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,
4130
42/// When `true` prevents `ZIG_PROGRESS` environment variable from being passed31/// When `true` prevents `ZIG_PROGRESS` environment variable from being passed
43/// to the child process, which otherwise would be used for the child to send32/// to the child process, which otherwise would be used for the child to send
...@@ -111,6 +100,20 @@ rebuilt_executable: ?Path,...@@ -111,6 +100,20 @@ rebuilt_executable: ?Path,
111/// If this Run step was produced by a Compile step, it is tracked here.100/// If this Run step was produced by a Compile step, it is tracked here.
112producer: ?*Step.Compile,101producer: ?*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
114pub const StdIn = union(enum) {117pub const StdIn = union(enum) {
115 none,118 none,
116 bytes: []const u8,119 bytes: []const u8,
...@@ -564,7 +567,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void {...@@ -564,7 +567,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void {
564 if (prev_path) |pp| {567 if (prev_path) |pp| {
565 const new_path = b.fmt("{s}{c}{s}", .{568 const new_path = b.fmt("{s}{c}{s}", .{
566 pp,569 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,
568 search_path,571 search_path,
569 });572 });
570 env_map.put(key, new_path) catch @panic("OOM");573 env_map.put(key, new_path) catch @panic("OOM");
...@@ -747,7 +750,7 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {...@@ -747,7 +750,7 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {
747fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {750fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {
748 const b = run.step.owner;751 const b = run.step.owner;
749 const path_str = path.toString(b.graph.arena) catch @panic("OOM");752 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)) {
751 // Absolute paths don't need changing.754 // Absolute paths don't need changing.
752 return path_str;755 return path_str;
753 }756 }
...@@ -755,19 +758,19 @@ fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {...@@ -755,19 +758,19 @@ fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {
755 const child_lazy_cwd = run.cwd orelse break :rel path_str;758 const child_lazy_cwd = run.cwd orelse break :rel path_str;
756 const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM");759 const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM");
757 // Convert it from relative to *our* cwd, to relative to the *child's* cwd.760 // 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");
759 };762 };
760 // Not every path can be made relative, e.g. if the path and the child cwd are on different763 // Not every path can be made relative, e.g. if the path and the child cwd are on different
761 // disk designators on Windows. In that case, `relative` will return an absolute path which we can764 // disk designators on Windows. In that case, `relative` will return an absolute path which we can
762 // just return.765 // just return.
763 if (std.fs.path.isAbsolute(child_cwd_rel)) {766 if (Dir.path.isAbsolute(child_cwd_rel)) {
764 return child_cwd_rel;767 return child_cwd_rel;
765 }768 }
766 // We're not done yet. In some cases this path must be prefixed with './':769 // We're not done yet. In some cases this path must be prefixed with './':
767 // * On POSIX, the executable name cannot be a single component like 'foo'770 // * On POSIX, the executable name cannot be a single component like 'foo'
768 // * Some executables might treat a leading '-' like a flag, which we must avoid771 // * Some executables might treat a leading '-' like a flag, which we must avoid
769 // There's no harm in it, so just *always* apply this prefix.772 // 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");
771}774}
772775
773const IndexedOutput = struct {776const IndexedOutput = struct {
...@@ -845,13 +848,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -845,13 +848,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
845 errdefer result.deinit();848 errdefer result.deinit();
846 result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory;849 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| {
849 return step.fail(852 return step.fail(
850 "unable to open input file '{f}': {t}",853 "unable to open input file '{f}': {t}",
851 .{ file_path, err },854 .{ file_path, err },
852 );855 );
853 };856 };
854 defer file.close();857 defer file.close(io);
855858
856 var buf: [1024]u8 = undefined;859 var buf: [1024]u8 = undefined;
857 var file_reader = file.reader(io, &buf);860 var file_reader = file.reader(io, &buf);
...@@ -964,15 +967,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -964,15 +967,15 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
964 &digest,967 &digest,
965 );968 );
966969
967 const output_dir_path = "o" ++ fs.path.sep_str ++ &digest;970 const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest;
968 for (output_placeholders.items) |placeholder| {971 for (output_placeholders.items) |placeholder| {
969 const output_sub_path = b.pathJoin(&.{ output_dir_path, placeholder.output.basename });972 const output_sub_path = b.pathJoin(&.{ output_dir_path, placeholder.output.basename });
970 const output_sub_dir_path = switch (placeholder.tag) {973 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).?,
972 .output_directory => output_sub_path,975 .output_directory => output_sub_path,
973 else => unreachable,976 else => unreachable,
974 };977 };
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| {
976 return step.fail("unable to make path '{f}{s}': {s}", .{979 return step.fail("unable to make path '{f}{s}': {s}", .{
977 b.cache_root, output_sub_dir_path, @errorName(err),980 b.cache_root, output_sub_dir_path, @errorName(err),
978 });981 });
...@@ -994,17 +997,17 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -994,17 +997,17 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
994997
995 // We do not know the final output paths yet, use temp paths to run the command.998 // We do not know the final output paths yet, use temp paths to run the command.
996 const rand_int = std.crypto.random.int(u64);999 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
999 for (output_placeholders.items) |placeholder| {1002 for (output_placeholders.items) |placeholder| {
1000 const output_components = .{ tmp_dir_path, placeholder.output.basename };1003 const output_components = .{ tmp_dir_path, placeholder.output.basename };
1001 const output_sub_path = b.pathJoin(&output_components);1004 const output_sub_path = b.pathJoin(&output_components);
1002 const output_sub_dir_path = switch (placeholder.tag) {1005 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).?,
1004 .output_directory => output_sub_path,1007 .output_directory => output_sub_path,
1005 else => unreachable,1008 else => unreachable,
1006 };1009 };
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| {
1008 return step.fail("unable to make path '{f}{s}': {s}", .{1011 return step.fail("unable to make path '{f}{s}': {s}", .{
1009 b.cache_root, output_sub_dir_path, @errorName(err),1012 b.cache_root, output_sub_dir_path, @errorName(err),
1010 });1013 });
...@@ -1022,7 +1025,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -1022,7 +1025,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
10221025
1023 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, options, null);1026 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();
1026 const dep_file_basename = dep_output_file.generated_file.getPath2(b, step);1029 const dep_file_basename = dep_output_file.generated_file.getPath2(b, step);
1027 if (has_side_effects)1030 if (has_side_effects)
1028 try man.addDepFile(dep_file_dir, dep_file_basename)1031 try man.addDepFile(dep_file_dir, dep_file_basename)
...@@ -1039,29 +1042,23 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -1039,29 +1042,23 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
10391042
1040 // Rename into place1043 // Rename into place
1041 if (any_output) {1044 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| {
1045 if (err == error.PathAlreadyExists) {1048 if (err == error.PathAlreadyExists) {
1046 b.cache_root.handle.deleteTree(o_sub_path) catch |del_err| {1049 b.cache_root.handle.deleteTree(io, o_sub_path) catch |del_err| {
1047 return step.fail("unable to remove dir '{f}'{s}: {s}", .{1050 return step.fail("unable to remove dir '{f}'{s}: {t}", .{
1048 b.cache_root,1051 b.cache_root, tmp_dir_path, del_err,
1049 tmp_dir_path,
1050 @errorName(del_err),
1051 });1052 });
1052 };1053 };
1053 b.cache_root.handle.rename(tmp_dir_path, o_sub_path) catch |retry_err| {1054 b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |retry_err| {
1054 return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{1055 return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{
1055 b.cache_root, tmp_dir_path,1056 b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, retry_err,
1056 b.cache_root, o_sub_path,
1057 @errorName(retry_err),
1058 });1057 });
1059 };1058 };
1060 } else {1059 } else {
1061 return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {s}", .{1060 return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{
1062 b.cache_root, tmp_dir_path,1061 b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, err,
1063 b.cache_root, o_sub_path,
1064 @errorName(err),
1065 });1062 });
1066 }1063 }
1067 };1064 };
...@@ -1110,8 +1107,8 @@ pub fn rerunInFuzzMode(...@@ -1110,8 +1107,8 @@ pub fn rerunInFuzzMode(
1110 errdefer result.deinit();1107 errdefer result.deinit();
1111 result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory;1108 result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory;
11121109
1113 const file = try file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{});1110 const file = try file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{});
1114 defer file.close();1111 defer file.close(io);
11151112
1116 var buf: [1024]u8 = undefined;1113 var buf: [1024]u8 = undefined;
1117 var file_reader = file.reader(io, &buf);1114 var file_reader = file.reader(io, &buf);
...@@ -1144,12 +1141,11 @@ pub fn rerunInFuzzMode(...@@ -1144,12 +1141,11 @@ pub fn rerunInFuzzMode(
11441141
1145 const has_side_effects = false;1142 const has_side_effects = false;
1146 const rand_int = std.crypto.random.int(u64);1143 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);
1148 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{1145 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{
1149 .progress_node = prog_node,1146 .progress_node = prog_node,
1150 .watch = undefined, // not used by `runCommand`1147 .watch = undefined, // not used by `runCommand`
1151 .web_server = null, // only needed for time reports1148 .web_server = null, // only needed for time reports
1152 .ttyconf = fuzz.ttyconf,
1153 .unit_test_timeout_ns = null, // don't time out fuzz tests for now1149 .unit_test_timeout_ns = null, // don't time out fuzz tests for now
1154 .gpa = fuzz.gpa,1150 .gpa = fuzz.gpa,
1155 }, .{1151 }, .{
...@@ -1240,6 +1236,7 @@ fn runCommand(...@@ -1240,6 +1236,7 @@ fn runCommand(
1240 const b = step.owner;1236 const b = step.owner;
1241 const arena = b.allocator;1237 const arena = b.allocator;
1242 const gpa = options.gpa;1238 const gpa = options.gpa;
1239 const io = b.graph.io;
12431240
1244 const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null;1241 const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null;
12451242
...@@ -1260,33 +1257,6 @@ fn runCommand(...@@ -1260,33 +1257,6 @@ fn runCommand(
1260 };1257 };
1261 defer env_map.deinit();1258 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
1290 const opt_generic_result = spawnChildAndCollect(run, argv, &env_map, has_side_effects, options, fuzz_context) catch |err| term: {1260 const opt_generic_result = spawnChildAndCollect(run, argv, &env_map, has_side_effects, options, fuzz_context) catch |err| term: {
1291 // InvalidExe: cpu arch mismatch1261 // InvalidExe: cpu arch mismatch
1292 // FileNotFound: can happen with a wrong dynamic linker path1262 // FileNotFound: can happen with a wrong dynamic linker path
...@@ -1308,7 +1278,7 @@ fn runCommand(...@@ -1308,7 +1278,7 @@ fn runCommand(
1308 const need_cross_libc = exe.is_linking_libc and1278 const need_cross_libc = exe.is_linking_libc and
1309 (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic));1279 (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic));
1310 const other_target = exe.root_module.resolved_target.?.result;1280 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, .{
1312 .qemu_fixes_dl = need_cross_libc and b.libc_runtimes_dir != null,1282 .qemu_fixes_dl = need_cross_libc and b.libc_runtimes_dir != null,
1313 .link_libc = exe.is_linking_libc,1283 .link_libc = exe.is_linking_libc,
1314 })) {1284 })) {
...@@ -1468,8 +1438,8 @@ fn runCommand(...@@ -1468,8 +1438,8 @@ fn runCommand(
1468 captured.output.generated_file.path = output_path;1438 captured.output.generated_file.path = output_path;
14691439
1470 const sub_path = b.pathJoin(&output_components);1440 const sub_path = b.pathJoin(&output_components);
1471 const sub_path_dirname = fs.path.dirname(sub_path).?;1441 const sub_path_dirname = Dir.path.dirname(sub_path).?;
1472 b.cache_root.handle.makePath(sub_path_dirname) catch |err| {1442 b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| {
1473 return step.fail("unable to make path '{f}{s}': {s}", .{1443 return step.fail("unable to make path '{f}{s}': {s}", .{
1474 b.cache_root, sub_path_dirname, @errorName(err),1444 b.cache_root, sub_path_dirname, @errorName(err),
1475 });1445 });
...@@ -1480,7 +1450,7 @@ fn runCommand(...@@ -1480,7 +1450,7 @@ fn runCommand(
1480 .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace),1450 .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace),
1481 .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace),1451 .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace),
1482 };1452 };
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| {
1484 return step.fail("unable to write file '{f}{s}': {s}", .{1454 return step.fail("unable to write file '{f}{s}': {s}", .{
1485 b.cache_root, sub_path, @errorName(err),1455 b.cache_root, sub_path, @errorName(err),
1486 });1456 });
...@@ -1589,6 +1559,8 @@ fn spawnChildAndCollect(...@@ -1589,6 +1559,8 @@ fn spawnChildAndCollect(
1589) !?EvalGenericResult {1559) !?EvalGenericResult {
1590 const b = run.step.owner;1560 const b = run.step.owner;
1591 const arena = b.allocator;1561 const arena = b.allocator;
1562 const graph = b.graph;
1563 const io = graph.io;
15921564
1593 if (fuzz_context != null) {1565 if (fuzz_context != null) {
1594 assert(!has_side_effects);1566 assert(!has_side_effects);
...@@ -1654,8 +1626,12 @@ fn spawnChildAndCollect(...@@ -1654,8 +1626,12 @@ fn spawnChildAndCollect(
1654 if (!run.disable_zig_progress and !inherit) {1626 if (!run.disable_zig_progress and !inherit) {
1655 child.progress_node = options.progress_node;1627 child.progress_node = options.progress_node;
1656 }1628 }
1657 if (inherit) std.debug.lockStdErr();1629 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {
1658 defer if (inherit) std.debug.unlockStdErr();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);
1659 var timer = try std.time.Timer.start();1635 var timer = try std.time.Timer.start();
1660 const res = try evalGeneric(run, &child);1636 const res = try evalGeneric(run, &child);
1661 run.step.result_duration_ns = timer.read();1637 run.step.result_duration_ns = timer.read();
...@@ -1663,6 +1639,35 @@ fn spawnChildAndCollect(...@@ -1663,6 +1639,35 @@ fn spawnChildAndCollect(
1663 }1639 }
1664}1640}
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
1666const StdioPollEnum = enum { stdout, stderr };1671const StdioPollEnum = enum { stdout, stderr };
16671672
1668fn evalZigTest(1673fn evalZigTest(
...@@ -1671,8 +1676,10 @@ fn evalZigTest(...@@ -1671,8 +1676,10 @@ fn evalZigTest(
1671 options: Step.MakeOptions,1676 options: Step.MakeOptions,
1672 fuzz_context: ?FuzzContext,1677 fuzz_context: ?FuzzContext,
1673) !EvalZigTestResult {1678) !EvalZigTestResult {
1674 const gpa = run.step.owner.allocator;1679 const step_owner = run.step.owner;
1675 const arena = run.step.owner.allocator;1680 const gpa = step_owner.allocator;
1681 const arena = step_owner.allocator;
1682 const io = step_owner.graph.io;
16761683
1677 // We will update this every time a child runs.1684 // We will update this every time a child runs.
1678 run.step.result_peak_rss = 0;1685 run.step.result_peak_rss = 0;
...@@ -1691,14 +1698,14 @@ fn evalZigTest(...@@ -1691,14 +1698,14 @@ fn evalZigTest(
1691 };1698 };
16921699
1693 while (true) {1700 while (true) {
1694 try child.spawn();1701 try child.spawn(io);
1695 var poller = std.Io.poll(gpa, StdioPollEnum, .{1702 var poller = std.Io.poll(gpa, StdioPollEnum, .{
1696 .stdout = child.stdout.?,1703 .stdout = child.stdout.?,
1697 .stderr = child.stderr.?,1704 .stderr = child.stderr.?,
1698 });1705 });
1699 var child_killed = false;1706 var child_killed = false;
1700 defer if (!child_killed) {1707 defer if (!child_killed) {
1701 _ = child.kill() catch {};1708 _ = child.kill(io) catch {};
1702 poller.deinit();1709 poller.deinit();
1703 run.step.result_peak_rss = @max(1710 run.step.result_peak_rss = @max(
1704 run.step.result_peak_rss,1711 run.step.result_peak_rss,
...@@ -1724,11 +1731,11 @@ fn evalZigTest(...@@ -1724,11 +1731,11 @@ fn evalZigTest(
1724 run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered());1731 run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered());
17251732
1726 // Clean up everything and wait for the child to exit.1733 // Clean up everything and wait for the child to exit.
1727 child.stdin.?.close();1734 child.stdin.?.close(io);
1728 child.stdin = null;1735 child.stdin = null;
1729 poller.deinit();1736 poller.deinit();
1730 child_killed = true;1737 child_killed = true;
1731 const term = try child.wait();1738 const term = try child.wait(io);
1732 run.step.result_peak_rss = @max(1739 run.step.result_peak_rss = @max(
1733 run.step.result_peak_rss,1740 run.step.result_peak_rss,
1734 child.resource_usage_statistics.getMaxRss() orelse 0,1741 child.resource_usage_statistics.getMaxRss() orelse 0,
...@@ -1744,11 +1751,11 @@ fn evalZigTest(...@@ -1744,11 +1751,11 @@ fn evalZigTest(
1744 poller.reader(.stderr).tossBuffered();1751 poller.reader(.stderr).tossBuffered();
17451752
1746 // Clean up everything and wait for the child to exit.1753 // Clean up everything and wait for the child to exit.
1747 child.stdin.?.close();1754 child.stdin.?.close(io);
1748 child.stdin = null;1755 child.stdin = null;
1749 poller.deinit();1756 poller.deinit();
1750 child_killed = true;1757 child_killed = true;
1751 const term = try child.wait();1758 const term = try child.wait(io);
1752 run.step.result_peak_rss = @max(1759 run.step.result_peak_rss = @max(
1753 run.step.result_peak_rss,1760 run.step.result_peak_rss,
1754 child.resource_usage_statistics.getMaxRss() orelse 0,1761 child.resource_usage_statistics.getMaxRss() orelse 0,
...@@ -1836,6 +1843,7 @@ fn pollZigTest(...@@ -1836,6 +1843,7 @@ fn pollZigTest(
1836 switch (ctx.fuzz.mode) {1843 switch (ctx.fuzz.mode) {
1837 .forever => {1844 .forever => {
1838 sendRunFuzzTestMessage(1845 sendRunFuzzTestMessage(
1846 io,
1839 child.stdin.?,1847 child.stdin.?,
1840 ctx.unit_test_index,1848 ctx.unit_test_index,
1841 .forever,1849 .forever,
...@@ -1844,6 +1852,7 @@ fn pollZigTest(...@@ -1844,6 +1852,7 @@ fn pollZigTest(
1844 },1852 },
1845 .limit => |limit| {1853 .limit => |limit| {
1846 sendRunFuzzTestMessage(1854 sendRunFuzzTestMessage(
1855 io,
1847 child.stdin.?,1856 child.stdin.?,
1848 ctx.unit_test_index,1857 ctx.unit_test_index,
1849 .iterations,1858 .iterations,
...@@ -1853,11 +1862,11 @@ fn pollZigTest(...@@ -1853,11 +1862,11 @@ fn pollZigTest(
1853 }1862 }
1854 } else if (opt_metadata.*) |*md| {1863 } else if (opt_metadata.*) |*md| {
1855 // Previous unit test process died or was killed; we're continuing where it left off1864 // 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 };
1857 } else {1866 } else {
1858 // Running unit tests normally1867 // Running unit tests normally
1859 run.fuzz_tests.clearRetainingCapacity();1868 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 };
1861 }1870 }
18621871
1863 var active_test_index: ?u32 = null;1872 var active_test_index: ?u32 = null;
...@@ -1973,7 +1982,7 @@ fn pollZigTest(...@@ -1973,7 +1982,7 @@ fn pollZigTest(
1973 active_test_index = null;1982 active_test_index = null;
1974 if (timer) |*t| t.reset();1983 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 };
1977 },1986 },
1978 .test_started => {1987 .test_started => {
1979 active_test_index = opt_metadata.*.?.next_index - 1;1988 active_test_index = opt_metadata.*.?.next_index - 1;
...@@ -2022,7 +2031,7 @@ fn pollZigTest(...@@ -2022,7 +2031,7 @@ fn pollZigTest(
2022 active_test_index = null;2031 active_test_index = null;
2023 if (timer) |*t| md.ns_per_test[tr_hdr.index] = t.lap();2032 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 };
2026 },2035 },
2027 .coverage_id => {2036 .coverage_id => {
2028 coverage_id = body_r.takeInt(u64, .little) catch unreachable;2037 coverage_id = body_r.takeInt(u64, .little) catch unreachable;
...@@ -2093,7 +2102,7 @@ pub const CachedTestMetadata = struct {...@@ -2093,7 +2102,7 @@ pub const CachedTestMetadata = struct {
2093 }2102 }
2094};2103};
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 {
2097 while (metadata.next_index < metadata.names.len) {2106 while (metadata.next_index < metadata.names.len) {
2098 const i = metadata.next_index;2107 const i = metadata.next_index;
2099 metadata.next_index += 1;2108 metadata.next_index += 1;
...@@ -2104,31 +2113,31 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr...@@ -2104,31 +2113,31 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr
2104 if (sub_prog_node.*) |n| n.end();2113 if (sub_prog_node.*) |n| n.end();
2105 sub_prog_node.* = metadata.prog_node.start(name, 0);2114 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);
2108 return;2117 return;
2109 } else {2118 } else {
2110 metadata.next_index = std.math.maxInt(u32); // indicate that all tests are done2119 metadata.next_index = std.math.maxInt(u32); // indicate that all tests are done
2111 try sendMessage(in, .exit);2120 try sendMessage(io, in, .exit);
2112 }2121 }
2113}2122}
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 {
2116 const header: std.zig.Client.Message.Header = .{2125 const header: std.zig.Client.Message.Header = .{
2117 .tag = tag,2126 .tag = tag,
2118 .bytes_len = 0,2127 .bytes_len = 0,
2119 };2128 };
2120 var w = file.writer(&.{});2129 var w = file.writer(io, &.{});
2121 w.interface.writeStruct(header, .little) catch |err| switch (err) {2130 w.interface.writeStruct(header, .little) catch |err| switch (err) {
2122 error.WriteFailed => return w.err.?,2131 error.WriteFailed => return w.err.?,
2123 };2132 };
2124}2133}
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 {
2127 const header: std.zig.Client.Message.Header = .{2136 const header: std.zig.Client.Message.Header = .{
2128 .tag = tag,2137 .tag = tag,
2129 .bytes_len = 4,2138 .bytes_len = 4,
2130 };2139 };
2131 var w = file.writer(&.{});2140 var w = file.writer(io, &.{});
2132 w.interface.writeStruct(header, .little) catch |err| switch (err) {2141 w.interface.writeStruct(header, .little) catch |err| switch (err) {
2133 error.WriteFailed => return w.err.?,2142 error.WriteFailed => return w.err.?,
2134 };2143 };
...@@ -2138,7 +2147,8 @@ fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index:...@@ -2138,7 +2147,8 @@ fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index:
2138}2147}
21392148
2140fn sendRunFuzzTestMessage(2149fn sendRunFuzzTestMessage(
2141 file: std.fs.File,2150 io: Io,
2151 file: Io.File,
2142 index: u32,2152 index: u32,
2143 kind: std.Build.abi.fuzz.LimitKind,2153 kind: std.Build.abi.fuzz.LimitKind,
2144 amount_or_instance: u64,2154 amount_or_instance: u64,
...@@ -2147,7 +2157,7 @@ fn sendRunFuzzTestMessage(...@@ -2147,7 +2157,7 @@ fn sendRunFuzzTestMessage(
2147 .tag = .start_fuzzing,2157 .tag = .start_fuzzing,
2148 .bytes_len = 4 + 1 + 8,2158 .bytes_len = 4 + 1 + 8,
2149 };2159 };
2150 var w = file.writer(&.{});2160 var w = file.writer(io, &.{});
2151 w.interface.writeStruct(header, .little) catch |err| switch (err) {2161 w.interface.writeStruct(header, .little) catch |err| switch (err) {
2152 error.WriteFailed => return w.err.?,2162 error.WriteFailed => return w.err.?,
2153 };2163 };
...@@ -2167,30 +2177,30 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {...@@ -2167,30 +2177,30 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
2167 const io = b.graph.io;2177 const io = b.graph.io;
2168 const arena = b.allocator;2178 const arena = b.allocator;
21692179
2170 try child.spawn();2180 try child.spawn(io);
2171 errdefer _ = child.kill() catch {};2181 errdefer _ = child.kill(io) catch {};
21722182
2173 try child.waitForSpawn();2183 try child.waitForSpawn();
21742184
2175 switch (run.stdin) {2185 switch (run.stdin) {
2176 .bytes => |bytes| {2186 .bytes => |bytes| {
2177 child.stdin.?.writeAll(bytes) catch |err| {2187 child.stdin.?.writeStreamingAll(io, bytes) catch |err| {
2178 return run.step.fail("unable to write stdin: {s}", .{@errorName(err)});2188 return run.step.fail("unable to write stdin: {t}", .{err});
2179 };2189 };
2180 child.stdin.?.close();2190 child.stdin.?.close(io);
2181 child.stdin = null;2191 child.stdin = null;
2182 },2192 },
2183 .lazy_path => |lazy_path| {2193 .lazy_path => |lazy_path| {
2184 const path = lazy_path.getPath3(b, &run.step);2194 const path = lazy_path.getPath3(b, &run.step);
2185 const file = path.root_dir.handle.openFile(path.subPathOrDot(), .{}) catch |err| {2195 const file = path.root_dir.handle.openFile(io, path.subPathOrDot(), .{}) catch |err| {
2186 return run.step.fail("unable to open stdin file: {s}", .{@errorName(err)});2196 return run.step.fail("unable to open stdin file: {t}", .{err});
2187 };2197 };
2188 defer file.close();2198 defer file.close(io);
2189 // TODO https://github.com/ziglang/zig/issues/239552199 // TODO https://github.com/ziglang/zig/issues/23955
2190 var read_buffer: [1024]u8 = undefined;2200 var read_buffer: [1024]u8 = undefined;
2191 var file_reader = file.reader(io, &read_buffer);2201 var file_reader = file.reader(io, &read_buffer);
2192 var write_buffer: [1024]u8 = undefined;2202 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);
2194 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {2204 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
2195 error.ReadFailed => return run.step.fail("failed to read from {f}: {t}", .{2205 error.ReadFailed => return run.step.fail("failed to read from {f}: {t}", .{
2196 path, file_reader.err.?,2206 path, file_reader.err.?,
...@@ -2204,7 +2214,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {...@@ -2204,7 +2214,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
2204 stdin_writer.err.?,2214 stdin_writer.err.?,
2205 }),2215 }),
2206 };2216 };
2207 child.stdin.?.close();2217 child.stdin.?.close(io);
2208 child.stdin = null;2218 child.stdin = null;
2209 },2219 },
2210 .none => {},2220 .none => {},
...@@ -2263,7 +2273,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {...@@ -2263,7 +2273,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
2263 run.step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0;2273 run.step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0;
22642274
2265 return .{2275 return .{
2266 .term = try child.wait(),2276 .term = try child.wait(io),
2267 .stdout = stdout_bytes,2277 .stdout = stdout_bytes,
2268 .stderr = stderr_bytes,2278 .stderr = stderr_bytes,
2269 };2279 };
...@@ -2276,7 +2286,7 @@ fn addPathForDynLibs(run: *Run, artifact: *Step.Compile) void {...@@ -2276,7 +2286,7 @@ fn addPathForDynLibs(run: *Run, artifact: *Step.Compile) void {
2276 if (compile.root_module.resolved_target.?.result.os.tag == .windows and2286 if (compile.root_module.resolved_target.?.result.os.tag == .windows and
2277 compile.isDynamicLibrary())2287 compile.isDynamicLibrary())
2278 {2288 {
2279 addPathDir(run, fs.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?);2289 addPathDir(run, Dir.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?);
2280 }2290 }
2281 }2291 }
2282}2292}
lib/std/Build/Step/UpdateSourceFiles.zig+3-3
...@@ -78,13 +78,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -78,13 +78,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
78 var any_miss = false;78 var any_miss = false;
79 for (usf.output_source_files.items) |output_source_file| {79 for (usf.output_source_files.items) |output_source_file| {
80 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {80 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| {
82 return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err });82 return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err });
83 };83 };
84 }84 }
85 switch (output_source_file.contents) {85 switch (output_source_file.contents) {
86 .bytes => |bytes| {86 .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| {
88 return step.fail("unable to write file '{f}{s}': {t}", .{88 return step.fail("unable to write file '{f}{s}': {t}", .{
89 b.build_root, output_source_file.sub_path, err,89 b.build_root, output_source_file.sub_path, err,
90 });90 });
...@@ -99,7 +99,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -99,7 +99,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
99 .cwd(),99 .cwd(),
100 io,100 io,
101 source_path,101 source_path,
102 b.build_root.handle.adaptToNewApi(),102 b.build_root.handle,
103 output_source_file.sub_path,103 output_source_file.sub_path,
104 .{},104 .{},
105 ) catch |err| {105 ) catch |err| {
lib/std/Build/Step/WriteFile.zig+15-22
...@@ -206,9 +206,9 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -206,9 +206,9 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
206 }206 }
207 }207 }
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);
210 var open_dirs_count: usize = 0;210 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
213 for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| {213 for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| {
214 man.hash.addBytes(dir.sub_path);214 man.hash.addBytes(dir.sub_path);
...@@ -218,7 +218,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -218,7 +218,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
218 const need_derived_inputs = try step.addDirectoryWatchInput(dir.source);218 const need_derived_inputs = try step.addDirectoryWatchInput(dir.source);
219 const src_dir_path = dir.source.getPath3(b, step);219 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| {
222 return step.fail("unable to open source directory '{f}': {s}", .{222 return step.fail("unable to open source directory '{f}': {s}", .{
223 src_dir_path, @errorName(err),223 src_dir_path, @errorName(err),
224 });224 });
...@@ -228,7 +228,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -228,7 +228,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
228228
229 var it = try src_dir.walk(gpa);229 var it = try src_dir.walk(gpa);
230 defer it.deinit();230 defer it.deinit();
231 while (try it.next()) |entry| {231 while (try it.next(io)) |entry| {
232 if (!dir.options.pathIncluded(entry.path)) continue;232 if (!dir.options.pathIncluded(entry.path)) continue;
233233
234 switch (entry.kind) {234 switch (entry.kind) {
...@@ -259,16 +259,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -259,16 +259,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
259259
260 write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest });260 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| {262 var cache_dir = b.cache_root.handle.createDirPathOpen(io, cache_path, .{}) catch |err|
263 return step.fail("unable to make path '{f}{s}': {s}", .{263 return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, cache_path, err });
264 b.cache_root, cache_path, @errorName(err),264 defer cache_dir.close(io);
265 });
266 };
267 defer cache_dir.close();
268265
269 for (write_file.files.items) |file| {266 for (write_file.files.items) |file| {
270 if (fs.path.dirname(file.sub_path)) |dirname| {267 if (fs.path.dirname(file.sub_path)) |dirname| {
271 cache_dir.makePath(dirname) catch |err| {268 cache_dir.createDirPath(io, dirname) catch |err| {
272 return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{269 return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{
273 b.cache_root, cache_path, fs.path.sep, dirname, err,270 b.cache_root, cache_path, fs.path.sep, dirname, err,
274 });271 });
...@@ -276,7 +273,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -276,7 +273,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
276 }273 }
277 switch (file.contents) {274 switch (file.contents) {
278 .bytes => |bytes| {275 .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| {
280 return step.fail("unable to write file '{f}{s}{c}{s}': {t}", .{277 return step.fail("unable to write file '{f}{s}{c}{s}': {t}", .{
281 b.cache_root, cache_path, fs.path.sep, file.sub_path, err,278 b.cache_root, cache_path, fs.path.sep, file.sub_path, err,
282 });279 });
...@@ -284,7 +281,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -284,7 +281,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
284 },281 },
285 .copy => |file_source| {282 .copy => |file_source| {
286 const source_path = file_source.getPath2(b, step);283 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| {
288 return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {t}", .{285 return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {t}", .{
289 source_path, b.cache_root, cache_path, fs.path.sep, file.sub_path, err,286 source_path, b.cache_root, cache_path, fs.path.sep, file.sub_path, err,
290 });287 });
...@@ -303,7 +300,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -303,7 +300,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
303 const dest_dirname = dir.sub_path;300 const dest_dirname = dir.sub_path;
304301
305 if (dest_dirname.len != 0) {302 if (dest_dirname.len != 0) {
306 cache_dir.makePath(dest_dirname) catch |err| {303 cache_dir.createDirPath(io, dest_dirname) catch |err| {
307 return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{304 return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{
308 b.cache_root, cache_path, fs.path.sep, dest_dirname, @errorName(err),305 b.cache_root, cache_path, fs.path.sep, dest_dirname, @errorName(err),
309 });306 });
...@@ -312,19 +309,19 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -312,19 +309,19 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
312309
313 var it = try already_open_dir.walk(gpa);310 var it = try already_open_dir.walk(gpa);
314 defer it.deinit();311 defer it.deinit();
315 while (try it.next()) |entry| {312 while (try it.next(io)) |entry| {
316 if (!dir.options.pathIncluded(entry.path)) continue;313 if (!dir.options.pathIncluded(entry.path)) continue;
317314
318 const src_entry_path = try src_dir_path.join(arena, entry.path);315 const src_entry_path = try src_dir_path.join(arena, entry.path);
319 const dest_path = b.pathJoin(&.{ dest_dirname, entry.path });316 const dest_path = b.pathJoin(&.{ dest_dirname, entry.path });
320 switch (entry.kind) {317 switch (entry.kind) {
321 .directory => try cache_dir.makePath(dest_path),318 .directory => try cache_dir.createDirPath(io, dest_path),
322 .file => {319 .file => {
323 const prev_status = Io.Dir.updateFile(320 const prev_status = Io.Dir.updateFile(
324 src_entry_path.root_dir.handle.adaptToNewApi(),321 src_entry_path.root_dir.handle,
325 io,322 io,
326 src_entry_path.sub_path,323 src_entry_path.sub_path,
327 cache_dir.adaptToNewApi(),324 cache_dir,
328 dest_path,325 dest_path,
329 .{},326 .{},
330 ) catch |err| {327 ) catch |err| {
...@@ -341,7 +338,3 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -341,7 +338,3 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
341338
342 try step.writeManifest(&man);339 try step.writeManifest(&man);
343}340}
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) {...@@ -122,7 +122,7 @@ const Os = switch (builtin.os.tag) {
122 }) catch return error.NameTooLong;122 }) catch return error.NameTooLong;
123 const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer);123 const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer);
124 stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle);124 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);
126 const stack_lfh: FileHandle = .{ .handle = stack_ptr };126 const stack_lfh: FileHandle = .{ .handle = stack_ptr };
127 return stack_lfh.clone(gpa);127 return stack_lfh.clone(gpa);
128 }128 }
...@@ -222,7 +222,7 @@ const Os = switch (builtin.os.tag) {...@@ -222,7 +222,7 @@ const Os = switch (builtin.os.tag) {
222 posix.fanotify_mark(fan_fd, .{222 posix.fanotify_mark(fan_fd, .{
223 .ADD = true,223 .ADD = true,
224 .ONLYDIR = true,224 .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| {
226 fatal("unable to watch {f}: {s}", .{ path, @errorName(err) });226 fatal("unable to watch {f}: {s}", .{ path, @errorName(err) });
227 };227 };
228 }228 }
...@@ -275,7 +275,7 @@ const Os = switch (builtin.os.tag) {...@@ -275,7 +275,7 @@ const Os = switch (builtin.os.tag) {
275 posix.fanotify_mark(fan_fd, .{275 posix.fanotify_mark(fan_fd, .{
276 .REMOVE = true,276 .REMOVE = true,
277 .ONLYDIR = true,277 .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) {
279 error.FileNotFound => {}, // Expected, harmless.279 error.FileNotFound => {}, // Expected, harmless.
280 else => |e| std.log.warn("unable to unwatch '{f}': {s}", .{ path, @errorName(e) }),280 else => |e| std.log.warn("unable to unwatch '{f}': {s}", .{ path, @errorName(e) }),
281 };281 };
...@@ -350,10 +350,10 @@ const Os = switch (builtin.os.tag) {...@@ -350,10 +350,10 @@ const Os = switch (builtin.os.tag) {
350 }350 }
351351
352 fn init(gpa: Allocator, path: Cache.Path) !*@This() {352 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)
354 // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW.354 // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW.
355 var dir_handle: windows.HANDLE = undefined;355 var dir_handle: windows.HANDLE = undefined;
356 const root_fd = path.root_dir.handle.fd;356 const root_fd = path.root_dir.handle.handle;
357 const sub_path = path.subPathOrDot();357 const sub_path = path.subPathOrDot();
358 const sub_path_w = try windows.sliceToPrefixedFileW(root_fd, sub_path);358 const sub_path_w = try windows.sliceToPrefixedFileW(root_fd, sub_path);
359 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;359 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) {...@@ -681,10 +681,10 @@ const Os = switch (builtin.os.tag) {
681 if (!gop.found_existing) {681 if (!gop.found_existing) {
682 const skip_open_dir = path.sub_path.len == 0;682 const skip_open_dir = path.sub_path.len == 0;
683 const dir_fd = if (skip_open_dir)683 const dir_fd = if (skip_open_dir)
684 path.root_dir.handle.fd684 path.root_dir.handle.handle
685 else685 else
686 posix.openat(path.root_dir.handle.fd, path.sub_path, dir_open_flags, 0) catch |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}: {s}", .{ path, @errorName(err) });687 fatal("failed to open directory {f}: {t}", .{ path, err });
688 };688 };
689 // Empirically the dir has to stay open or else no events are triggered.689 // Empirically the dir has to stay open or else no events are triggered.
690 errdefer if (!skip_open_dir) posix.close(dir_fd);690 errdefer if (!skip_open_dir) posix.close(dir_fd);
...@@ -750,7 +750,7 @@ const Os = switch (builtin.os.tag) {...@@ -750,7 +750,7 @@ const Os = switch (builtin.os.tag) {
750 // to access that data via the dir_fd field.750 // to access that data via the dir_fd field.
751 const path = w.dir_table.keys()[i];751 const path = w.dir_table.keys()[i];
752 const dir_fd = if (path.sub_path.len == 0)752 const dir_fd = if (path.sub_path.len == 0)
753 path.root_dir.handle.fd753 path.root_dir.handle.handle
754 else754 else
755 handles.items(.dir_fd)[i];755 handles.items(.dir_fd)[i];
756 assert(dir_fd != -1);756 assert(dir_fd != -1);
...@@ -761,7 +761,7 @@ const Os = switch (builtin.os.tag) {...@@ -761,7 +761,7 @@ const Os = switch (builtin.os.tag) {
761 const last_dir_fd = fd: {761 const last_dir_fd = fd: {
762 const last_path = w.dir_table.keys()[handles.len - 1];762 const last_path = w.dir_table.keys()[handles.len - 1];
763 const last_dir_fd = if (last_path.sub_path.len == 0)763 const last_dir_fd = if (last_path.sub_path.len == 0)
764 last_path.root_dir.handle.fd764 last_path.root_dir.handle.handle
765 else765 else
766 handles.items(.dir_fd)[handles.len - 1];766 handles.items(.dir_fd)[handles.len - 1];
767 assert(last_dir_fd != -1);767 assert(last_dir_fd != -1);
lib/std/Build/Watch/FsEvents.zig+3-2
...@@ -102,10 +102,10 @@ pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {...@@ -102,10 +102,10 @@ pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {
102 };102 };
103}103}
104104
105pub fn deinit(fse: *FsEvents, gpa: Allocator) void {105pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void {
106 dispatch_release(fse.waiting_semaphore);106 dispatch_release(fse.waiting_semaphore);
107 dispatch_release(fse.dispatch_queue);107 dispatch_release(fse.dispatch_queue);
108 fse.core_services.close();108 fse.core_services.close(io);
109109
110 gpa.free(fse.watch_roots);110 gpa.free(fse.watch_roots);
111 fse.watch_paths.deinit(gpa);111 fse.watch_paths.deinit(gpa);
...@@ -487,6 +487,7 @@ const FSEventStreamEventFlags = packed struct(u32) {...@@ -487,6 +487,7 @@ const FSEventStreamEventFlags = packed struct(u32) {
487};487};
488488
489const std = @import("std");489const std = @import("std");
490const Io = std.Io;
490const assert = std.debug.assert;491const assert = std.debug.assert;
491const Allocator = std.mem.Allocator;492const Allocator = std.mem.Allocator;
492const watch_log = std.log.scoped(.watch);493const watch_log = std.log.scoped(.watch);
lib/std/Build/WebServer.zig+17-19
...@@ -2,7 +2,6 @@ gpa: Allocator,...@@ -2,7 +2,6 @@ gpa: Allocator,
2graph: *const Build.Graph,2graph: *const Build.Graph,
3all_steps: []const *Build.Step,3all_steps: []const *Build.Step,
4listen_address: net.IpAddress,4listen_address: net.IpAddress,
5ttyconf: Io.tty.Config,
6root_prog_node: std.Progress.Node,5root_prog_node: std.Progress.Node,
7watch: bool,6watch: bool,
87
...@@ -52,7 +51,6 @@ pub fn notifyUpdate(ws: *WebServer) void {...@@ -52,7 +51,6 @@ pub fn notifyUpdate(ws: *WebServer) void {
5251
53pub const Options = struct {52pub const Options = struct {
54 gpa: Allocator,53 gpa: Allocator,
55 ttyconf: Io.tty.Config,
56 graph: *const std.Build.Graph,54 graph: *const std.Build.Graph,
57 all_steps: []const *Build.Step,55 all_steps: []const *Build.Step,
58 root_prog_node: std.Progress.Node,56 root_prog_node: std.Progress.Node,
...@@ -98,7 +96,6 @@ pub fn init(opts: Options) WebServer {...@@ -98,7 +96,6 @@ pub fn init(opts: Options) WebServer {
9896
99 return .{97 return .{
100 .gpa = opts.gpa,98 .gpa = opts.gpa,
101 .ttyconf = opts.ttyconf,
102 .graph = opts.graph,99 .graph = opts.graph,
103 .all_steps = all_steps,100 .all_steps = all_steps,
104 .listen_address = opts.listen_address,101 .listen_address = opts.listen_address,
...@@ -129,6 +126,7 @@ pub fn init(opts: Options) WebServer {...@@ -129,6 +126,7 @@ pub fn init(opts: Options) WebServer {
129}126}
130pub fn deinit(ws: *WebServer) void {127pub fn deinit(ws: *WebServer) void {
131 const gpa = ws.gpa;128 const gpa = ws.gpa;
129 const io = ws.graph.io;
132130
133 gpa.free(ws.step_names_trailing);131 gpa.free(ws.step_names_trailing);
134 gpa.free(ws.step_status_bits);132 gpa.free(ws.step_status_bits);
...@@ -139,7 +137,7 @@ pub fn deinit(ws: *WebServer) void {...@@ -139,7 +137,7 @@ pub fn deinit(ws: *WebServer) void {
139 gpa.free(ws.time_report_update_times);137 gpa.free(ws.time_report_update_times);
140138
141 if (ws.serve_thread) |t| {139 if (ws.serve_thread) |t| {
142 if (ws.tcp_server) |*s| s.stream.close();140 if (ws.tcp_server) |*s| s.stream.close(io);
143 t.join();141 t.join();
144 }142 }
145 if (ws.tcp_server) |*s| s.deinit();143 if (ws.tcp_server) |*s| s.deinit();
...@@ -217,9 +215,9 @@ pub fn finishBuild(ws: *WebServer, opts: struct {...@@ -217,9 +215,9 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
217 else => {},215 else => {},
218 }216 }
219 if (@bitSizeOf(usize) != 64) {217 if (@bitSizeOf(usize) != 64) {
220 // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,218 // Current implementation depends on posix.mmap()'s second
221 // being compatible with `std.fs.getEndPos() u64`'s return value. This is not the case219 // parameter, `length: usize`, being compatible with file system's
222 // on 32-bit platforms.220 // u64 return value. This is not the case on 32-bit platforms.
223 // Affects or affected by issues #5185, #22523, and #22464.221 // Affects or affected by issues #5185, #22523, and #22464.
224 std.process.fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});222 std.process.fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)});
225 }223 }
...@@ -232,7 +230,6 @@ pub fn finishBuild(ws: *WebServer, opts: struct {...@@ -232,7 +230,6 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
232 ws.fuzz = Fuzz.init(230 ws.fuzz = Fuzz.init(
233 ws.gpa,231 ws.gpa,
234 ws.graph.io,232 ws.graph.io,
235 ws.ttyconf,
236 ws.all_steps,233 ws.all_steps,
237 ws.root_prog_node,234 ws.root_prog_node,
238 .{ .forever = .{ .ws = ws } },235 .{ .forever = .{ .ws = ws } },
...@@ -468,11 +465,12 @@ pub fn serveFile(...@@ -468,11 +465,12 @@ pub fn serveFile(
468 content_type: []const u8,465 content_type: []const u8,
469) !void {466) !void {
470 const gpa = ws.gpa;467 const gpa = ws.gpa;
468 const io = ws.graph.io;
471 // The desired API is actually sendfile, which will require enhancing http.Server.469 // The desired API is actually sendfile, which will require enhancing http.Server.
472 // We load the file with every request so that the user can make changes to the file470 // We load the file with every request so that the user can make changes to the file
473 // and refresh the HTML page without restarting this server.471 // 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| {472 const file_contents = path.root_dir.handle.readFileAlloc(io, path.sub_path, gpa, .limited(10 * 1024 * 1024)) catch |err| {
475 log.err("failed to read '{f}': {s}", .{ path, @errorName(err) });473 log.err("failed to read '{f}': {t}", .{ path, err });
476 return error.AlreadyReported;474 return error.AlreadyReported;
477 };475 };
478 defer gpa.free(file_contents);476 defer gpa.free(file_contents);
...@@ -503,14 +501,14 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons...@@ -503,14 +501,14 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
503 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };501 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
504502
505 for (paths) |path| {503 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| {
507 log.err("failed to open '{f}': {s}", .{ path, @errorName(err) });505 log.err("failed to open '{f}': {s}", .{ path, @errorName(err) });
508 continue;506 continue;
509 };507 };
510 defer file.close();508 defer file.close(io);
511 const stat = try file.stat();509 const stat = try file.stat(io);
512 var read_buffer: [1024]u8 = undefined;510 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
515 // TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can513 // TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can
516 // be cwd-relative. This is also related to why linkification doesn't work in the fuzzer UI:514 // be cwd-relative. This is also related to why linkification doesn't work in the fuzzer UI:
...@@ -578,7 +576,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -578,7 +576,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
578 child.stdin_behavior = .Pipe;576 child.stdin_behavior = .Pipe;
579 child.stdout_behavior = .Pipe;577 child.stdout_behavior = .Pipe;
580 child.stderr_behavior = .Pipe;578 child.stderr_behavior = .Pipe;
581 try child.spawn();579 try child.spawn(io);
582580
583 var poller = Io.poll(gpa, enum { stdout, stderr }, .{581 var poller = Io.poll(gpa, enum { stdout, stderr }, .{
584 .stdout = child.stdout.?,582 .stdout = child.stdout.?,
...@@ -586,7 +584,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -586,7 +584,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
586 });584 });
587 defer poller.deinit();585 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, &.{
590 .{ .tag = .update, .bytes_len = 0 },588 .{ .tag = .update, .bytes_len = 0 },
591 .{ .tag = .exit, .bytes_len = 0 },589 .{ .tag = .exit, .bytes_len = 0 },
592 })));590 })));
...@@ -634,10 +632,10 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -634,10 +632,10 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
634 }632 }
635633
636 // Send EOF to stdin.634 // Send EOF to stdin.
637 child.stdin.?.close();635 child.stdin.?.close(io);
638 child.stdin = null;636 child.stdin = null;
639637
640 switch (try child.wait()) {638 switch (try child.wait(io)) {
641 .Exited => |code| {639 .Exited => |code| {
642 if (code != 0) {640 if (code != 0) {
643 log.err(641 log.err(
...@@ -657,7 +655,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -657,7 +655,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
657 }655 }
658656
659 if (result_error_bundle.errorMessageCount() > 0) {657 if (result_error_bundle.errorMessageCount() > 0) {
660 result_error_bundle.renderToStdErr(.{}, .auto);658 try result_error_bundle.renderToStderr(io, .{}, .auto);
661 log.err("the following command failed with {d} compilation errors:\n{s}", .{659 log.err("the following command failed with {d} compilation errors:\n{s}", .{
662 result_error_bundle.errorMessageCount(),660 result_error_bundle.errorMessageCount(),
663 try Build.Step.allocPrintCmd(arena, null, argv.items),661 try Build.Step.allocPrintCmd(arena, null, argv.items),
lib/std/Io.zig+104-28
...@@ -82,8 +82,6 @@ pub const Limit = enum(usize) {...@@ -82,8 +82,6 @@ pub const Limit = enum(usize) {
82pub const Reader = @import("Io/Reader.zig");82pub const Reader = @import("Io/Reader.zig");
83pub const Writer = @import("Io/Writer.zig");83pub const Writer = @import("Io/Writer.zig");
8484
85pub const tty = @import("Io/tty.zig");
86
87pub fn poll(85pub fn poll(
88 gpa: Allocator,86 gpa: Allocator,
89 comptime StreamEnum: type,87 comptime StreamEnum: type,
...@@ -528,14 +526,13 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -528,14 +526,13 @@ pub fn Poller(comptime StreamEnum: type) type {
528/// Given an enum, returns a struct with fields of that enum, each field526/// Given an enum, returns a struct with fields of that enum, each field
529/// representing an I/O stream for polling.527/// representing an I/O stream for polling.
530pub fn PollFiles(comptime StreamEnum: type) type {528pub 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(.{}));
532}530}
533531
534test {532test {
535 _ = net;533 _ = net;
536 _ = Reader;534 _ = Reader;
537 _ = Writer;535 _ = Writer;
538 _ = tty;
539 _ = Evented;536 _ = Evented;
540 _ = Threaded;537 _ = Threaded;
541 _ = @import("Io/test.zig");538 _ = @import("Io/test.zig");
...@@ -662,27 +659,66 @@ pub const VTable = struct {...@@ -662,27 +659,66 @@ pub const VTable = struct {
662 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,659 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
663 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,660 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,662 dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void,
666 dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,663 dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus,
667 dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,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,
668 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,666 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,667 dirStatFile: *const fn (?*anyopaque, Dir, []const u8, Dir.StatFileOptions) Dir.StatFileError!File.Stat,
670 dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void,668 dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void,
671 dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,669 dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File,
672 dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,670 dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File,
673 dirOpenDir: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.OpenError!Dir,671 dirClose: *const fn (?*anyopaque, []const Dir) void,
674 dirClose: *const fn (?*anyopaque, 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
675 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,688 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
676 fileClose: *const fn (?*anyopaque, File) void,689 fileLength: *const fn (?*anyopaque, File) File.LengthError!u64,
677 fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize,690 fileClose: *const fn (?*anyopaque, []const File) void,
678 fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize,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,
679 /// Returns 0 on end of stream.695 /// 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,
681 /// Returns 0 on end of stream.697 /// 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,
683 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,699 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
684 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,700 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
687 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,723 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
688 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,724 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
...@@ -698,7 +734,8 @@ pub const VTable = struct {...@@ -698,7 +734,8 @@ pub const VTable = struct {
698 /// Returns 0 on end of stream.734 /// Returns 0 on end of stream.
699 netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize,735 netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize,
700 netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,736 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,
702 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,739 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
703 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,740 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
704 netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void,741 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{...@@ -723,6 +760,7 @@ pub const UnexpectedError = error{
723760
724pub const Dir = @import("Io/Dir.zig");761pub const Dir = @import("Io/Dir.zig");
725pub const File = @import("Io/File.zig");762pub const File = @import("Io/File.zig");
763pub const Terminal = @import("Io/Terminal.zig");
726764
727pub const Clock = enum {765pub const Clock = enum {
728 /// A settable system-wide clock that measures real (i.e. wall-clock)766 /// 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...@@ -1277,17 +1315,21 @@ pub fn futexWait(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, e
1277/// wakeups are possible. It remains the caller's responsibility to differentiate between these1315/// wakeups are possible. It remains the caller's responsibility to differentiate between these
1278/// three possible wake-up reasons if necessary.1316/// three possible wake-up reasons if necessary.
1279pub fn futexWaitTimeout(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, expected: T, timeout: Timeout) Cancelable!void {1317pub 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);1318 const expected_int: u32 = switch (@typeInfo(T)) {
1281 const expected_raw: *align(1) const u32 = @ptrCast(&expected);1319 .@"enum" => @bitCast(@intFromEnum(expected)),
1282 return io.vtable.futexWait(io.userdata, @ptrCast(ptr), expected_raw.*, timeout);1320 else => @bitCast(expected),
1321 };
1322 return io.vtable.futexWait(io.userdata, @ptrCast(ptr), expected_int, timeout);
1283}1323}
1284/// Same as `futexWait`, except does not introduce a cancelation point.1324/// Same as `futexWait`, except does not introduce a cancelation point.
1285///1325///
1286/// For a description of cancelation and cancelation points, see `Future.cancel`.1326/// For a description of cancelation and cancelation points, see `Future.cancel`.
1287pub fn futexWaitUncancelable(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, expected: T) void {1327pub fn futexWaitUncancelable(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, expected: T) void {
1288 comptime assert(@sizeOf(T) == @sizeOf(u32));1328 const expected_int: u32 = switch (@typeInfo(T)) {
1289 const expected_raw: *align(1) const u32 = @ptrCast(&expected);1329 .@"enum" => @bitCast(@intFromEnum(expected)),
1290 io.vtable.futexWaitUncancelable(io.userdata, @ptrCast(ptr), expected_raw.*);1330 else => @bitCast(expected),
1331 };
1332 io.vtable.futexWaitUncancelable(io.userdata, @ptrCast(ptr), expected_int);
1291}1333}
1292/// Unblocks pending futex waits on `ptr`, up to a limit of `max_waiters` calls.1334/// Unblocks pending futex waits on `ptr`, up to a limit of `max_waiters` calls.
1293pub fn futexWake(io: Io, comptime T: type, ptr: *align(@alignOf(u32)) const T, max_waiters: u32) void {1335pub 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) {...@@ -1539,10 +1581,12 @@ pub const Event = enum(u32) {
1539 }1581 }
1540 }1582 }
15411583
1584 pub const WaitTimeoutError = error{Timeout} || Cancelable;
1585
1542 /// Blocks the calling thread until either the logical boolean is set, the timeout expires, or a1586 /// Blocks the calling thread until either the logical boolean is set, the timeout expires, or a
1543 /// spurious wakeup occurs. If the timeout expires or a spurious wakeup occurs, `error.Timeout`1587 /// spurious wakeup occurs. If the timeout expires or a spurious wakeup occurs, `error.Timeout`
1544 /// is returned.1588 /// 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 {
1546 if (@cmpxchgStrong(Event, event, .unset, .waiting, .acquire, .acquire)) |prev| switch (prev) {1590 if (@cmpxchgStrong(Event, event, .unset, .waiting, .acquire, .acquire)) |prev| switch (prev) {
1547 .unset => unreachable,1591 .unset => unreachable,
1548 .waiting => assert(!builtin.single_threaded), // invalid state1592 .waiting => assert(!builtin.single_threaded), // invalid state
...@@ -1555,7 +1599,7 @@ pub const Event = enum(u32) {...@@ -1555,7 +1599,7 @@ pub const Event = enum(u32) {
1555 // waiters would wake up when a *new waiter* was added. So it's easiest to just leave1599 // waiters would wake up when a *new waiter* was added. So it's easiest to just leave
1556 // the state at `.waiting`---at worst it causes one redundant call to `futexWake`.1600 // the state at `.waiting`---at worst it causes one redundant call to `futexWake`.
1557 }1601 }
1558 io.futexWaitTimeout(Event, event, .waiting, timeout);1602 try io.futexWaitTimeout(Event, event, .waiting, timeout);
1559 switch (@atomicLoad(Event, event, .acquire)) {1603 switch (@atomicLoad(Event, event, .acquire)) {
1560 .unset => unreachable, // `reset` called before pending `wait` returned1604 .unset => unreachable, // `reset` called before pending `wait` returned
1561 .waiting => return error.Timeout,1605 .waiting => return error.Timeout,
...@@ -2136,3 +2180,35 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {...@@ -2136,3 +2180,35 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
2136 else => unreachable,2180 else => unreachable,
2137 }2181 }
2138}2182}
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 @@...@@ -1,4 +1,5 @@
1const Dir = @This();1const Dir = @This();
2const root = @import("root");
23
3const builtin = @import("builtin");4const builtin = @import("builtin");
4const native_os = builtin.os.tag;5const native_os = builtin.os.tag;
...@@ -6,11 +7,71 @@ const native_os = builtin.os.tag;...@@ -6,11 +7,71 @@ const native_os = builtin.os.tag;
6const std = @import("../std.zig");7const std = @import("../std.zig");
7const Io = std.Io;8const Io = std.Io;
8const File = Io.File;9const File = Io.File;
10const assert = std.debug.assert;
11const Allocator = std.mem.Allocator;
912
10handle: Handle,13handle: Handle,
1114
12pub const Mode = Io.File.Mode;15pub const path = std.fs.path;
13pub const default_mode: Mode = 0o755;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
15/// Returns a handle to the current working directory.76/// Returns a handle to the current working directory.
16///77///
...@@ -20,6 +81,8 @@ pub const default_mode: Mode = 0o755;...@@ -20,6 +81,8 @@ pub const default_mode: Mode = 0o755;
20/// Closing the returned `Dir` is checked illegal behavior.81/// Closing the returned `Dir` is checked illegal behavior.
21///82///
22/// On POSIX targets, this function is comptime-callable.83/// On POSIX targets, this function is comptime-callable.
84///
85/// On WASI, the value this returns is application-configurable.
23pub fn cwd() Dir {86pub fn cwd() Dir {
24 return switch (native_os) {87 return switch (native_os) {
25 .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },88 .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },
...@@ -28,9 +91,314 @@ pub fn cwd() Dir {...@@ -28,9 +91,314 @@ pub fn cwd() Dir {
28 };91 };
29}92}
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
31pub const Handle = std.posix.fd_t;397pub const Handle = std.posix.fd_t;
32398
33pub const PathNameError = error{399pub const PathNameError = error{
400 /// Returned when an insufficient buffer is provided that cannot fit the
401 /// path name.
34 NameTooLong,402 NameTooLong,
35 /// File system cannot encode the requested file name bytes.403 /// File system cannot encode the requested file name bytes.
36 /// Could be due to invalid WTF-8 on Windows, invalid UTF-8 on WASI,404 /// 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...@@ -69,6 +437,11 @@ pub fn access(dir: Dir, io: Io, sub_path: []const u8, options: AccessOptions) Ac
69 return io.vtable.dirAccess(io.userdata, dir, sub_path, options);437 return io.vtable.dirAccess(io.userdata, dir, sub_path, options);
70}438}
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
72pub const OpenError = error{445pub const OpenError = error{
73 FileNotFound,446 FileNotFound,
74 NotDir,447 NotDir,
...@@ -108,8 +481,17 @@ pub fn openDir(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) Ope...@@ -108,8 +481,17 @@ pub fn openDir(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) Ope
108 return io.vtable.dirOpenDir(io.userdata, dir, sub_path, options);481 return io.vtable.dirOpenDir(io.userdata, dir, sub_path, options);
109}482}
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
111pub fn close(dir: Dir, io: Io) void {489pub 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);
113}495}
114496
115/// Opens a file for reading or writing, without attempting to create a new file.497/// 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...@@ -125,6 +507,11 @@ pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) F
125 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);507 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);
126}508}
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
128/// Creates, opens, or overwrites a file with write access.515/// Creates, opens, or overwrites a file with write access.
129///516///
130/// Allocates a resource to be dellocated with `File.close`.517/// 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...@@ -136,6 +523,10 @@ pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlag
136 return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags);523 return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags);
137}524}
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
139pub const WriteFileOptions = struct {530pub const WriteFileOptions = struct {
140 /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).531 /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
141 /// On WASI, `sub_path` should be encoded as valid UTF-8.532 /// On WASI, `sub_path` should be encoded as valid UTF-8.
...@@ -145,13 +536,13 @@ pub const WriteFileOptions = struct {...@@ -145,13 +536,13 @@ pub const WriteFileOptions = struct {
145 flags: File.CreateFlags = .{},536 flags: File.CreateFlags = .{},
146};537};
147538
148pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable;539pub const WriteFileError = File.Writer.Error || File.OpenError;
149540
150/// Writes content to the file system, using the file creation flags provided.541/// Writes content to the file system, using the file creation flags provided.
151pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {542pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
152 var file = try dir.createFile(io, options.sub_path, options.flags);543 var file = try dir.createFile(io, options.sub_path, options.flags);
153 defer file.close(io);544 defer file.close(io);
154 try file.writeAll(io, options.data);545 try file.writeStreamingAll(io, options.data);
155}546}
156547
157pub const PrevStatus = enum {548pub const PrevStatus = enum {
...@@ -161,10 +552,10 @@ pub const PrevStatus = enum {...@@ -161,10 +552,10 @@ pub const PrevStatus = enum {
161552
162pub const UpdateFileError = File.OpenError;553pub const UpdateFileError = File.OpenError;
163554
164/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If555/// Check the file size, mtime, and permissions of `source_path` and `dest_path`. If
165/// they are equal, does nothing. Otherwise, atomically copies `source_path` to556/// they are equal, does nothing. Otherwise, atomically copies `source_path` to
166/// `dest_path`, creating the parent directory hierarchy as needed. The557/// `dest_path`, creating the parent directory hierarchy as needed. The
167/// destination file gains the mtime, atime, and mode of the source file so558/// destination file gains the mtime, atime, and permissions of the source file so
168/// that the next call to `updateFile` will not need a copy.559/// that the next call to `updateFile` will not need a copy.
169///560///
170/// Returns the previous status of the file before updating.561/// Returns the previous status of the file before updating.
...@@ -179,13 +570,13 @@ pub fn updateFile(...@@ -179,13 +570,13 @@ pub fn updateFile(
179 dest_dir: Dir,570 dest_dir: Dir,
180 /// If directories in this path do not exist, they are created.571 /// If directories in this path do not exist, they are created.
181 dest_path: []const u8,572 dest_path: []const u8,
182 options: std.fs.Dir.CopyFileOptions,573 options: CopyFileOptions,
183) !PrevStatus {574) !PrevStatus {
184 var src_file = try source_dir.openFile(io, source_path, .{});575 var src_file = try source_dir.openFile(io, source_path, .{});
185 defer src_file.close(io);576 defer src_file.close(io);
186577
187 const src_stat = try src_file.stat(io);578 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;
189 check_dest_stat: {580 check_dest_stat: {
190 const dest_stat = blk: {581 const dest_stat = blk: {
191 var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {582 var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {
...@@ -199,19 +590,19 @@ pub fn updateFile(...@@ -199,19 +590,19 @@ pub fn updateFile(
199590
200 if (src_stat.size == dest_stat.size and591 if (src_stat.size == dest_stat.size and
201 src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and592 src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and
202 actual_mode == dest_stat.mode)593 actual_permissions == dest_stat.permissions)
203 {594 {
204 return .fresh;595 return .fresh;
205 }596 }
206 }597 }
207598
208 if (std.fs.path.dirname(dest_path)) |dirname| {599 if (path.dirname(dest_path)) |dirname| {
209 try dest_dir.makePath(io, dirname);600 try dest_dir.createDirPath(io, dirname);
210 }601 }
211602
212 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.603 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, .{604 var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
214 .mode = actual_mode,605 .permissions = actual_permissions,
215 .write_buffer = &buffer,606 .write_buffer = &buffer,
216 });607 });
217 defer atomic_file.deinit();608 defer atomic_file.deinit();
...@@ -224,7 +615,7 @@ pub fn updateFile(...@@ -224,7 +615,7 @@ pub fn updateFile(
224 error.WriteFailed => return atomic_file.file_writer.err.?,615 error.WriteFailed => return atomic_file.file_writer.err.?,
225 };616 };
226 try atomic_file.flush();617 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);
228 try atomic_file.renameIntoPlace();619 try atomic_file.renameIntoPlace();
229 return .stale;620 return .stale;
230}621}
...@@ -242,7 +633,11 @@ pub const ReadFileError = File.OpenError || File.Reader.Error;...@@ -242,7 +633,11 @@ pub const ReadFileError = File.OpenError || File.Reader.Error;
242/// * On WASI, `file_path` should be encoded as valid UTF-8.633/// * On WASI, `file_path` should be encoded as valid UTF-8.
243/// * On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.634/// * On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
244pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileError![]u8 {635pub 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 });
246 defer file.close(io);641 defer file.close(io);
247642
248 var reader = file.reader(io, &.{});643 var reader = file.reader(io, &.{});
...@@ -253,7 +648,7 @@ pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileE...@@ -253,7 +648,7 @@ pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileE
253 return buffer[0..n];648 return buffer[0..n];
254}649}
255650
256pub const MakeError = error{651pub const CreateDirError = error{
257 /// In WASI, this error may occur when the file descriptor does652 /// In WASI, this error may occur when the file descriptor does
258 /// not hold the required rights to create a new directory relative to it.653 /// not hold the required rights to create a new directory relative to it.
259 AccessDenied,654 AccessDenied,
...@@ -279,21 +674,36 @@ pub const MakeError = error{...@@ -279,21 +674,36 @@ pub const MakeError = error{
279/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.674/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
280///675///
281/// Related:676/// Related:
282/// * `makePath`677/// * `createDirPath`
283/// * `makeDirAbsolute`678/// * `createDirAbsolute`
284pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void {679pub fn createDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) CreateDirError!void {
285 return io.vtable.dirMake(io.userdata, dir, sub_path, default_mode);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);
286}694}
287695
288pub const MakePathError = MakeError || StatPathError;696test createDirAbsolute {}
289697
290/// Calls makeDir iteratively to make an entire path, creating any parent698pub const CreateDirPathError = CreateDirError || StatFileError;
291/// directories that do not exist.699
700/// Creates parent directories with default permissions as necessary to ensure
701/// `sub_path` exists as a directory.
292///702///
293/// Returns success if the path already exists and is a directory.703/// Returns success if the path already exists and is a directory.
294///704///
295/// This function is not atomic, and if it returns an error, the file system705/// This function may not be atomic. If it returns an error, the file system
296/// may have been modified regardless.706/// may have been modified.
297///707///
298/// Fails on an empty path with `error.BadPathName` as that is not a path that708/// Fails on an empty path with `error.BadPathName` as that is not a path that
299/// can be created.709/// can be created.
...@@ -309,48 +719,29 @@ pub const MakePathError = MakeError || StatPathError;...@@ -309,48 +719,29 @@ pub const MakePathError = MakeError || StatPathError;
309/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,719/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
310/// meaning a `sub_path` like "first/../second" will create both a `./first`720/// meaning a `sub_path` like "first/../second" will create both a `./first`
311/// and a `./second` directory.721/// and a `./second` directory.
312pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void {722///
313 _ = try makePathStatus(dir, io, sub_path);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);
314}727}
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 was731/// Same as `createDirPath` except returns whether the path already existed or was
319/// successfully created.732/// successfully created.
320pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus {733pub fn createDirPathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) CreateDirPathError!CreatePathStatus {
321 var it = std.fs.path.componentIterator(sub_path);734 return io.vtable.dirCreateDirPath(io.userdata, dir, sub_path, permissions);
322 var status: MakePathStatus = .existed;
323 var component = it.last() orelse return error.BadPathName;
324 while (true) {
325 if (makeDir(dir, io, component.path)) {
326 status = .created;
327 } else |err| switch (err) {
328 error.PathAlreadyExists => {
329 // stat the file and return an error if it's not a directory
330 // this is important because otherwise a dangling symlink
331 // could cause an infinite loop
332 check_dir: {
333 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
334 const fstat = statPath(dir, io, component.path, .{}) catch |stat_err| switch (stat_err) {
335 error.IsDir => break :check_dir,
336 else => |e| return e,
337 };
338 if (fstat.kind != .directory) return error.NotDir;
339 }
340 },
341 error.FileNotFound => |e| {
342 component = it.previous() orelse return e;
343 continue;
344 },
345 else => |e| return e,
346 }
347 component = it.next() orelse return status;
348 }
349}735}
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.
354///745///
355/// When this operation is canceled, it may leave the file system in a746/// When this operation is canceled, it may leave the file system in a
356/// partially modified state.747/// partially modified state.
...@@ -358,8 +749,8 @@ pub const MakeOpenPathError = MakeError || OpenError || StatPathError;...@@ -358,8 +749,8 @@ pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
358/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).749/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
359/// On WASI, `sub_path` should be encoded as valid UTF-8.750/// On WASI, `sub_path` should be encoded as valid UTF-8.
360/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.751/// 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 {752pub fn createDirPathOpen(dir: Dir, io: Io, sub_path: []const u8, options: CreateDirPathOpenOptions) CreateDirPathOpenError!Dir {
362 return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options);753 return io.vtable.dirCreateDirPathOpen(io.userdata, dir, sub_path, options.permissions, options.open_options);
363}754}
364755
365pub const Stat = File.Stat;756pub const Stat = File.Stat;
...@@ -369,9 +760,9 @@ pub fn stat(dir: Dir, io: Io) StatError!Stat {...@@ -369,9 +760,9 @@ pub fn stat(dir: Dir, io: Io) StatError!Stat {
369 return io.vtable.dirStat(io.userdata, dir);760 return io.vtable.dirStat(io.userdata, dir);
370}761}
371762
372pub const StatPathError = File.OpenError || File.StatError;763pub const StatFileError = File.OpenError || File.StatError;
373764
374pub const StatPathOptions = struct {765pub const StatFileOptions = struct {
375 follow_symlinks: bool = true,766 follow_symlinks: bool = true,
376};767};
377768
...@@ -387,6 +778,1074 @@ pub const StatPathOptions = struct {...@@ -387,6 +778,1074 @@ pub const StatPathOptions = struct {
387/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).778/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
388/// * On WASI, `sub_path` should be encoded as valid UTF-8.779/// * On WASI, `sub_path` should be encoded as valid UTF-8.
389/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.780/// * 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 {781pub fn statFile(dir: Dir, io: Io, sub_path: []const u8, options: StatFileOptions) StatFileError!Stat {
391 return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);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);
392}1851}
lib/std/Io/File.zig+446-407
...@@ -7,12 +7,19 @@ const is_windows = native_os == .windows;...@@ -7,12 +7,19 @@ const is_windows = native_os == .windows;
7const std = @import("../std.zig");7const std = @import("../std.zig");
8const Io = std.Io;8const Io = std.Io;
9const assert = std.debug.assert;9const assert = std.debug.assert;
10const Dir = std.Io.Dir;
1011
11handle: Handle,12handle: Handle,
1213
14pub const Reader = @import("File/Reader.zig");
15pub const Writer = @import("File/Writer.zig");
16pub const Atomic = @import("File/Atomic.zig");
17
13pub const Handle = std.posix.fd_t;18pub const Handle = std.posix.fd_t;
14pub const Mode = std.posix.mode_t;
15pub const INode = std.posix.ino_t;19pub 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
17pub const Kind = enum {24pub const Kind = enum {
18 block_device,25 block_device,
...@@ -41,9 +48,9 @@ pub const Stat = struct {...@@ -41,9 +48,9 @@ pub const Stat = struct {
41 /// The FileIndex on Windows is similar. It is a number for a file that48 /// The FileIndex on Windows is similar. It is a number for a file that
42 /// is unique to each filesystem.49 /// is unique to each filesystem.
43 inode: INode,50 inode: INode,
51 nlink: NLink,
44 size: u64,52 size: u64,
45 /// This is available on POSIX systems and is always 0 otherwise.53 permissions: Permissions,
46 mode: Mode,
47 kind: Kind,54 kind: Kind,
48 /// Last access time in nanoseconds, relative to UTC 1970-01-01.55 /// Last access time in nanoseconds, relative to UTC 1970-01-01.
49 atime: Io.Timestamp,56 atime: Io.Timestamp,
...@@ -95,6 +102,26 @@ pub const Lock = enum {...@@ -95,6 +102,26 @@ pub const Lock = enum {
95pub const OpenFlags = struct {102pub const OpenFlags = struct {
96 mode: OpenMode = .read_only,103 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
98 /// Open the file with an advisory lock to coordinate with other processes125 /// Open the file with an advisory lock to coordinate with other processes
99 /// accessing it at the same time. An exclusive lock will prevent other126 /// accessing it at the same time. An exclusive lock will prevent other
100 /// processes from acquiring a lock. A shared lock will prevent other127 /// processes from acquiring a lock. A shared lock will prevent other
...@@ -141,7 +168,51 @@ pub const OpenFlags = struct {...@@ -141,7 +168,51 @@ pub const OpenFlags = struct {
141 }168 }
142};169};
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
146pub const OpenError = error{217pub const OpenError = error{
147 SharingViolation,218 SharingViolation,
...@@ -149,7 +220,6 @@ pub const OpenError = error{...@@ -149,7 +220,6 @@ pub const OpenError = error{
149 NoDevice,220 NoDevice,
150 /// On Windows, `\\server` or `\\server\share` was not found.221 /// On Windows, `\\server` or `\\server\share` was not found.
151 NetworkNotFound,222 NetworkNotFound,
152 ProcessNotFound,
153 /// On Windows, antivirus software is enabled by default. It can be223 /// On Windows, antivirus software is enabled by default. It can be
154 /// disabled, but Windows Update sometimes ignores the user's preference224 /// disabled, but Windows Update sometimes ignores the user's preference
155 /// and re-enables it. When enabled, antivirus software on Windows225 /// and re-enables it. When enabled, antivirus software on Windows
...@@ -178,7 +248,9 @@ pub const OpenError = error{...@@ -178,7 +248,9 @@ pub const OpenError = error{
178 /// The file is too large to be opened. This error is unreachable248 /// The file is too large to be opened. This error is unreachable
179 /// for 64-bit targets, as well as when opening directories.249 /// for 64-bit targets, as well as when opening directories.
180 FileTooBig,250 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.
182 IsDir,254 IsDir,
183 /// A new path cannot be created because the device has no room for the new file.255 /// A new path cannot be created because the device has no room for the new file.
184 /// This error is only reachable when the `CREAT` flag is provided.256 /// This error is only reachable when the `CREAT` flag is provided.
...@@ -189,7 +261,7 @@ pub const OpenError = error{...@@ -189,7 +261,7 @@ pub const OpenError = error{
189 /// The path already exists and the `CREAT` and `EXCL` flags were provided.261 /// The path already exists and the `CREAT` and `EXCL` flags were provided.
190 PathAlreadyExists,262 PathAlreadyExists,
191 DeviceBusy,263 DeviceBusy,
192 FileLocksNotSupported,264 FileLocksUnsupported,
193 /// One of these three things:265 /// One of these three things:
194 /// * pathname refers to an executable image which is currently being266 /// * pathname refers to an executable image which is currently being
195 /// executed and write access was requested.267 /// executed and write access was requested.
...@@ -204,451 +276,418 @@ pub const OpenError = error{...@@ -204,451 +276,418 @@ pub const OpenError = error{
204} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;276} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
205277
206pub fn close(file: File, io: Io) void {278pub 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]);
208}280}
209281
210pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || std.posix.FlockError;282pub fn closeMany(io: Io, files: []const File) void {
211283 return io.vtable.fileClose(io.userdata, files);
212pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File {
213 return io.vtable.openSelfExe(io.userdata, flags);
214}284}
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 {293/// Blocks until all pending file contents and metadata modifications for the
219 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);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);
220}300}
221301
222pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable;302/// Test whether the file refers to a terminal (similar to libc "isatty").
223303///
224pub fn writeStreaming(file: File, io: Io, buffer: [][]const u8) WriteStreamingError!usize {304/// See also:
225 return file.fileWriteStreaming(io, buffer);305/// * `enableAnsiEscapeCodes`
306/// * `supportsAnsiEscapeCodes`.
307pub fn isTty(file: File, io: Io) Io.Cancelable!bool {
308 return io.vtable.fileIsTty(io.userdata, file);
226}309}
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 {315pub fn enableAnsiEscapeCodes(file: File, io: Io) EnableAnsiEscapeCodesError!void {
231 return io.vtable.fileWritePositional(io.userdata, file, buffer, offset);316 return io.vtable.fileEnableAnsiEscapeCodes(io.userdata, file);
232}317}
233318
234pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File {319/// Test whether ANSI escape codes will be treated as such without
235 assert(std.fs.path.isAbsolute(absolute_path));320/// attempting to enable support for ANSI escape codes.
236 return Io.Dir.cwd().openFile(io, absolute_path, flags);321pub fn supportsAnsiEscapeCodes(file: File, io: Io) Io.Cancelable!bool {
322 return io.vtable.fileSupportsAnsiEscapeCodes(io.userdata, file);
237}323}
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.
240///335///
241/// Positional is more threadsafe, since the global seek position is not336/// The file offset after this call is left unchanged.
242/// affected.337pub fn setLength(file: File, io: Io, new_length: u64) SetLengthError!void {
243pub fn reader(file: File, io: Io, buffer: []u8) Reader {338 return io.vtable.fileSetLength(io.userdata, file, new_length);
244 return .init(file, io, buffer);
245}339}
246340
247/// Positional is more threadsafe, since the global seek position is not341pub const LengthError = StatError;
248/// affected, but when such syscalls are not available, preemptively342
249/// initializing in streaming mode skips a failed syscall.343/// Retrieve the ending byte index of the file.
250pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {344///
251 return .initStreaming(file, io, buffer);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);
252}348}
253349
254pub const SeekError = error{350pub const SetPermissionsError = error{
255 Unseekable,
256 /// The file descriptor does not hold the required rights to seek on it.
257 AccessDenied,351 AccessDenied,
352 PermissionDenied,
353 InputOutput,
354 SymLinkLoop,
355 FileNotFound,
356 SystemResources,
357 ReadOnlyFileSystem,
258} || Io.Cancelable || Io.UnexpectedError;358} || Io.Cancelable || Io.UnexpectedError;
259359
260/// Memoizes key information about a file handle such as:360/// Also known as "chmod".
261/// * The size from calling stat, or the error that occurred therein.
262/// * The current seek position.
263/// * The error that occurred when trying to seek.
264/// * Whether reading should be done positionally or streaming.
265/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`)
266/// versus plain variants (e.g. `read`).
267///361///
268/// Fulfills the `Io.Reader` interface.362/// The process must have the correct privileges in order to do this
269pub const Reader = struct {363/// successfully, or must have the effective user ID matching the owner of the
270 io: Io,364/// file.
271 file: File,365pub fn setPermissions(file: File, io: Io, new_permissions: Permissions) SetPermissionsError!void {
272 err: ?Error = null,366 return io.vtable.fileSetPermissions(io.userdata, file, new_permissions);
273 mode: Reader.Mode = .positional,367}
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 }
358368
359 pub fn init(file: File, io: Io, buffer: []u8) Reader {369pub const SetOwnerError = error{
360 return .{370 AccessDenied,
361 .io = io,371 PermissionDenied,
362 .file = file,372 InputOutput,
363 .interface = initInterface(buffer),373 SymLinkLoop,
364 };374 FileNotFound,
365 }375 SystemResources,
376 ReadOnlyFileSystem,
377} || Io.Cancelable || Io.UnexpectedError;
366378
367 /// Takes a legacy `std.fs.File` to help with upgrading.379/// Also known as "chown".
368 pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader {380///
369 return .init(.{ .handle = file.handle }, io, buffer);381/// The process must have the correct privileges in order to do this
370 }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 {389/// Cross-platform representation of permissions on a file.
373 return .{390///
374 .io = io,391/// On POSIX systems this corresponds to "mode" and on Windows this corresponds to "attributes".
375 .file = file,392pub const Permissions = std.Options.FilePermissions orelse if (is_windows) enum(std.os.windows.DWORD) {
376 .interface = initInterface(buffer),393 default_file = 0,
377 .size = size,394 _,
378 };395
379 }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 not402 pub fn toAttributes(self: @This()) windows.FILE.ATTRIBUTE {
382 /// affected, but when such syscalls are not available, preemptively403 return @bitCast(@intFromEnum(self));
383 /// initializing in streaming mode skips a failed syscall.
384 pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader {
385 return .{
386 .io = io,
387 .file = file,
388 .interface = Reader.initInterface(buffer),
389 .mode = .streaming,
390 .seek_err = error.Unseekable,
391 .size_err = error.Streaming,
392 };
393 }404 }
394405
395 pub fn getSize(r: *Reader) SizeError!u64 {406 pub fn readOnly(self: @This()) bool {
396 return r.size orelse {407 const attributes = toAttributes(self);
397 if (r.size_err) |err| return err;408 return attributes & windows.FILE_ATTRIBUTE_READONLY != 0;
398 if (stat(r.file, r.io)) |st| {
399 if (st.kind == .file) {
400 r.size = st.size;
401 return st.size;
402 } else {
403 r.mode = r.mode.toStreaming();
404 r.size_err = error.Streaming;
405 return error.Streaming;
406 }
407 } else |err| {
408 r.size_err = err;
409 return err;
410 }
411 };
412 }409 }
413410
414 pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {411 pub fn setReadOnly(self: @This(), read_only: bool) @This() {
415 const io = r.io;412 const attributes = toAttributes(self);
416 switch (r.mode) {413 return @enumFromInt(if (read_only)
417 .positional, .positional_reading => {414 attributes | windows.FILE_ATTRIBUTE_READONLY
418 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));415 else
419 },416 attributes & ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY));
420 .streaming, .streaming_reading => {417 }
421 const seek_err = r.seek_err orelse e: {418} else if (std.posix.mode_t != u0) enum(std.posix.mode_t) {
422 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) {419 /// This is the default mode given to POSIX operating systems for creating
423 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));420 /// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
424 return;421 /// since most people would expect "-rw-r--r--", for example, when using
425 } else |err| {422 /// the `touch` command, which would correspond to `0o644`. However, POSIX
426 r.seek_err = err;423 /// libc implementations use `0o666` inside `fopen` and then rely on the
427 break :e err;424 /// process-scoped "umask" setting to adjust this number for file creation.
428 }425 default_file = 0o666,
429 };426 default_dir = 0o755,
430 var remaining = std.math.cast(u64, offset) orelse return seek_err;427 executable_file = 0o777,
431 while (remaining > 0) {428 _,
432 remaining -= discard(&r.interface, .limited64(remaining)) catch |err| {429
433 r.seek_err = err;430 pub const has_executable_bit = native_os != .wasi;
434 return err;431
435 };432 pub fn toMode(self: @This()) std.posix.mode_t {
436 }433 return @intFromEnum(self);
437 r.interface.tossBuffered();
438 },
439 .failure => return r.seek_err.?,
440 }
441 }434 }
442435
443 /// Repositions logical read offset relative to the beginning of the file.436 pub fn fromMode(mode: std.posix.mode_t) @This() {
444 pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {437 return @enumFromInt(mode);
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 }
462 }438 }
463439
464 pub fn logicalPos(r: *const Reader) u64 {440 /// Returns `true` if and only if no class has write permissions.
465 return r.pos - r.interface.bufferedLen();441 pub fn readOnly(self: @This()) bool {
442 const mode = toMode(self);
443 return mode & 0o222 == 0;
466 }444 }
467445
468 fn setLogicalPos(r: *Reader, offset: u64) void {446 /// Enables write permission for all classes.
469 const logical_pos = r.logicalPos();447 pub fn setReadOnly(self: @This(), read_only: bool) @This() {
470 if (offset < logical_pos or offset >= r.pos) {448 const mode = toMode(self);
471 r.interface.tossBuffered();449 const o222 = @as(std.posix.mode_t, 0o222);
472 r.pos = offset;450 return @enumFromInt(if (read_only) mode & ~o222 else mode | o222);
473 } else r.interface.toss(@intCast(offset - logical_pos));
474 }451 }
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 byte459pub const SetTimestampsError = error{
477 /// vectors through the underlying read calls as possible.460 /// times is NULL, or both nsec values are UTIME_NOW, and either:
478 const max_buffers_len = 16;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/// The granularity that ultimately is stored depends on the combination of
481 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));482/// operating system and file system. When a value as provided that exceeds
482 return streamMode(r, w, limit, r.mode);483/// this range, the value is clamped to the maximum.
483 }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 {493/// Sets the accessed and modification timestamps of `file` to the current wall
486 switch (mode) {494/// clock time.
487 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {495///
488 error.Unimplemented => {496/// The granularity that ultimately is stored depends on the combination of
489 r.mode = r.mode.toReading();497/// operating system and file system.
490 return 0;498pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {
491 },499 return io.vtable.fileSetTimestampsNow(io.userdata, file);
492 else => |e| return e,500}
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 }
511501
512 fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {502/// Returns 0 on stream end or if `buffer` has no space available for data.
513 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));503///
514 switch (r.mode) {504/// See also:
515 .positional, .positional_reading => return readVecPositional(r, data),505/// * `reader`
516 .streaming, .streaming_reading => return readVecStreaming(r, data),506pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize {
517 .failure => return error.ReadFailed,507 return io.vtable.fileReadStreaming(io.userdata, file, buffer);
518 }508}
519 }
520509
521 fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize {510pub const ReadPositionalError = Reader.Error || error{Unseekable};
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 }
556511
557 fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {512/// Returns 0 on stream end or if `buffer` has no space available for data.
558 const io = r.io;513///
559 var iovecs_buffer: [max_buffers_len][]u8 = undefined;514/// See also:
560 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);515/// * `reader`
561 const dest = iovecs_buffer[0..dest_n];516pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) ReadPositionalError!usize {
562 assert(dest[0].len > 0);517 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
563 const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| {518}
564 r.err = err;519
565 return error.ReadFailed;520pub const WritePositionalError = Writer.Error || error{Unseekable};
566 };521
567 if (n == 0) {522/// See also:
568 r.size = r.pos;523/// * `writer`
569 return error.EndOfStream;524pub fn writePositional(file: File, io: Io, buffer: []const []const u8, offset: u64) WritePositionalError!usize {
570 }525 return io.vtable.fileWritePositional(io.userdata, file, &.{}, buffer, 1, offset);
571 r.pos += n;526}
572 if (n > data_size) {527
573 r.interface.end += n - data_size;528/// Equivalent to creating a positional writer, writing `bytes`, and then flushing.
574 return data_size;529pub fn writePositionalAll(file: File, io: Io, bytes: []const u8, offset: u64) WritePositionalError!void {
575 }530 var index: usize = 0;
576 return n;531 while (index < bytes.len)
577 }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 {554/// Equivalent to creating a positional reader and reading multiple times to fill `buffer`.
580 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));555///
581 const io = r.io;556/// Returns number of bytes read into `buffer`. If less than `buffer.len`, end of file occurred.
582 const file = r.file;557///
583 switch (r.mode) {558/// See also:
584 .positional, .positional_reading => {559/// * `reader`
585 const size = r.getSize() catch {560pub fn readPositionalAll(file: File, io: Io, buffer: []u8, offset: u64) ReadPositionalError!usize {
586 r.mode = r.mode.toStreaming();561 var index: usize = 0;
587 return 0;562 while (index != buffer.len) {
588 };563 const amt = try file.readPositional(io, &.{buffer[index..]}, offset + index);
589 const logical_pos = logicalPos(r);564 if (amt == 0) break;
590 const delta = @min(@intFromEnum(limit), size - logical_pos);565 index += amt;
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 }
646 }566 }
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.580/// Defaults to positional reading; falls back to streaming.
649 pub fn atEnd(r: *Reader) bool {581///
650 // Even if stat fails, size is set when end is encountered.582/// Positional is more threadsafe, since the global seek position is not
651 const size = r.size orelse return false;583/// affected.
652 return size - logicalPos(r) == 0;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);
653 }600 }
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(...@@ -1093,7 +1093,7 @@ fn createFile(
1093 .PERM => return error.PermissionDenied,1093 .PERM => return error.PermissionDenied,
1094 .EXIST => return error.PathAlreadyExists,1094 .EXIST => return error.PathAlreadyExists,
1095 .BUSY => return error.DeviceBusy,1095 .BUSY => return error.DeviceBusy,
1096 .OPNOTSUPP => return error.FileLocksNotSupported,1096 .OPNOTSUPP => return error.FileLocksUnsupported,
1097 .AGAIN => return error.WouldBlock,1097 .AGAIN => return error.WouldBlock,
1098 .TXTBSY => return error.FileBusy,1098 .TXTBSY => return error.FileBusy,
1099 .NXIO => return error.NoDevice,1099 .NXIO => return error.NoDevice,
...@@ -1201,7 +1201,7 @@ fn fileOpen(...@@ -1201,7 +1201,7 @@ fn fileOpen(
1201 .PERM => return error.PermissionDenied,1201 .PERM => return error.PermissionDenied,
1202 .EXIST => return error.PathAlreadyExists,1202 .EXIST => return error.PathAlreadyExists,
1203 .BUSY => return error.DeviceBusy,1203 .BUSY => return error.DeviceBusy,
1204 .OPNOTSUPP => return error.FileLocksNotSupported,1204 .OPNOTSUPP => return error.FileLocksUnsupported,
1205 .AGAIN => return error.WouldBlock,1205 .AGAIN => return error.WouldBlock,
1206 .TXTBSY => return error.FileBusy,1206 .TXTBSY => return error.FileBusy,
1207 .NXIO => return error.NoDevice,1207 .NXIO => return error.NoDevice,
lib/std/Io/Kqueue.zig+10-10
...@@ -869,11 +869,11 @@ pub fn io(k: *Kqueue) Io {...@@ -869,11 +869,11 @@ pub fn io(k: *Kqueue) Io {
869 .conditionWaitUncancelable = conditionWaitUncancelable,869 .conditionWaitUncancelable = conditionWaitUncancelable,
870 .conditionWake = conditionWake,870 .conditionWake = conditionWake,
871871
872 .dirMake = dirMake,872 .dirCreateDir = dirCreateDir,
873 .dirMakePath = dirMakePath,873 .dirCreateDirPath = dirCreateDirPath,
874 .dirMakeOpenPath = dirMakeOpenPath,874 .dirCreateDirPathOpen = dirCreateDirPathOpen,
875 .dirStat = dirStat,875 .dirStat = dirStat,
876 .dirStatPath = dirStatPath,876 .dirStatFile = dirStatFile,
877877
878 .fileStat = fileStat,878 .fileStat = fileStat,
879 .dirAccess = dirAccess,879 .dirAccess = dirAccess,
...@@ -888,7 +888,7 @@ pub fn io(k: *Kqueue) Io {...@@ -888,7 +888,7 @@ pub fn io(k: *Kqueue) Io {
888 .fileReadPositional = fileReadPositional,888 .fileReadPositional = fileReadPositional,
889 .fileSeekBy = fileSeekBy,889 .fileSeekBy = fileSeekBy,
890 .fileSeekTo = fileSeekTo,890 .fileSeekTo = fileSeekTo,
891 .openSelfExe = openSelfExe,891 .openExecutable = openExecutable,
892892
893 .now = now,893 .now = now,
894 .sleep = sleep,894 .sleep = sleep,
...@@ -1114,7 +1114,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition....@@ -1114,7 +1114,7 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
1114 k.yield(waiting_fiber, .reschedule);1114 k.yield(waiting_fiber, .reschedule);
1115}1115}
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 {
1118 const k: *Kqueue = @ptrCast(@alignCast(userdata));1118 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1119 _ = k;1119 _ = k;
1120 _ = dir;1120 _ = dir;
...@@ -1122,7 +1122,7 @@ fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode...@@ -1122,7 +1122,7 @@ fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode
1122 _ = mode;1122 _ = mode;
1123 @panic("TODO");1123 @panic("TODO");
1124}1124}
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 {
1126 const k: *Kqueue = @ptrCast(@alignCast(userdata));1126 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1127 _ = k;1127 _ = k;
1128 _ = dir;1128 _ = dir;
...@@ -1130,7 +1130,7 @@ fn dirMakePath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir....@@ -1130,7 +1130,7 @@ fn dirMakePath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.
1130 _ = mode;1130 _ = mode;
1131 @panic("TODO");1131 @panic("TODO");
1132}1132}
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 {
1134 const k: *Kqueue = @ptrCast(@alignCast(userdata));1134 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1135 _ = k;1135 _ = k;
1136 _ = dir;1136 _ = dir;
...@@ -1144,7 +1144,7 @@ fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {...@@ -1144,7 +1144,7 @@ fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
1144 _ = dir;1144 _ = dir;
1145 @panic("TODO");1145 @panic("TODO");
1146}1146}
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 {
1148 const k: *Kqueue = @ptrCast(@alignCast(userdata));1148 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1149 _ = k;1149 _ = k;
1150 _ = dir;1150 _ = dir;
...@@ -1246,7 +1246,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, absolute_offset: u64) File.Seek...@@ -1246,7 +1246,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, absolute_offset: u64) File.Seek
1246 _ = absolute_offset;1246 _ = absolute_offset;
1247 @panic("TODO");1247 @panic("TODO");
1248}1248}
1249fn openSelfExe(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenSelfExeError!File {1249fn openExecutable(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenExecutableError!File {
1250 const k: *Kqueue = @ptrCast(@alignCast(userdata));1250 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1251 _ = k;1251 _ = k;
1252 _ = file;1252 _ = 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();...@@ -3,19 +3,22 @@ const Threaded = @This();
3const builtin = @import("builtin");3const builtin = @import("builtin");
4const native_os = builtin.os.tag;4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;5const is_windows = native_os == .windows;
6const windows = std.os.windows;6const is_darwin = native_os.isDarwin();
7const ws2_32 = std.os.windows.ws2_32;
8const is_debug = builtin.mode == .Debug;7const is_debug = builtin.mode == .Debug;
98
10const std = @import("../std.zig");9const std = @import("../std.zig");
11const Io = std.Io;10const Io = std.Io;
12const net = std.Io.net;11const net = std.Io.net;
12const File = std.Io.File;
13const Dir = std.Io.Dir;
13const HostName = std.Io.net.HostName;14const HostName = std.Io.net.HostName;
14const IpAddress = std.Io.net.IpAddress;15const IpAddress = std.Io.net.IpAddress;
15const Allocator = std.mem.Allocator;16const Allocator = std.mem.Allocator;
16const Alignment = std.mem.Alignment;17const Alignment = std.mem.Alignment;
17const assert = std.debug.assert;18const assert = std.debug.assert;
18const posix = std.posix;19const posix = std.posix;
20const windows = std.os.windows;
21const ws2_32 = std.os.windows.ws2_32;
1922
20/// Thread-safe.23/// Thread-safe.
21allocator: Allocator,24allocator: Allocator,
...@@ -26,23 +29,7 @@ join_requested: bool = false,...@@ -26,23 +29,7 @@ join_requested: bool = false,
26stack_size: usize,29stack_size: usize,
27/// All threads are spawned detached; this is how we wait until they all exit.30/// All threads are spawned detached; this is how we wait until they all exit.
28wait_group: std.Thread.WaitGroup = .{},31wait_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`.
39async_limit: Io.Limit,32async_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`.
46concurrent_limit: Io.Limit = .unlimited,33concurrent_limit: Io.Limit = .unlimited,
47/// Error from calling `std.Thread.getCpuCount` in `init`.34/// Error from calling `std.Thread.getCpuCount` in `init`.
48cpu_count_error: ?std.Thread.CpuCountError,35cpu_count_error: ?std.Thread.CpuCountError,
...@@ -52,17 +39,7 @@ cpu_count_error: ?std.Thread.CpuCountError,...@@ -52,17 +39,7 @@ cpu_count_error: ?std.Thread.CpuCountError,
52busy_count: usize = 0,39busy_count: usize = 0,
53main_thread: Thread,40main_thread: Thread,
54pid: Pid = .unknown,41pid: Pid = .unknown,
55/// When a cancel request is made, blocking syscalls can be unblocked by42robust_cancel: RobustCancel,
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,
6643
67wsa: if (is_windows) Wsa else struct {} = .{},44wsa: if (is_windows) Wsa else struct {} = .{},
6845
...@@ -70,6 +47,64 @@ have_signal_handler: bool,...@@ -70,6 +47,64 @@ have_signal_handler: bool,
70old_sig_io: if (have_sig_io) posix.Sigaction else void,47old_sig_io: if (have_sig_io) posix.Sigaction else void,
71old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,48old_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
73pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {108pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
74 enabled,109 enabled,
75 disabled,110 disabled,
...@@ -82,6 +117,42 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) {...@@ -82,6 +117,42 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
82 _,117 _,
83} else enum(u0) { unknown = 0 };118} 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
85const Thread = struct {156const Thread = struct {
86 /// The value that needs to be passed to pthread_kill or tgkill in order to157 /// The value that needs to be passed to pthread_kill or tgkill in order to
87 /// send a signal.158 /// send a signal.
...@@ -164,13 +235,6 @@ const Thread = struct {...@@ -164,13 +235,6 @@ const Thread = struct {
164 ) orelse return;235 ) orelse return;
165 }236 }
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
174 fn currentSignalId() SignaleeId {238 fn currentSignalId() SignaleeId {
175 return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId();239 return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId();
176 }240 }
...@@ -229,7 +293,7 @@ const Thread = struct {...@@ -229,7 +293,7 @@ const Thread = struct {
229 .INTR => {}, // caller's responsibility to retry293 .INTR => {}, // caller's responsibility to retry
230 .AGAIN => {}, // ptr.* != expect294 .AGAIN => {}, // ptr.* != expect
231 .INVAL => {}, // possibly timeout overflow295 .INVAL => {}, // possibly timeout overflow
232 .TIMEDOUT => {}, // timeout296 .TIMEDOUT => {},
233 .FAULT => recoverableOsBugDetected(), // ptr was invalid297 .FAULT => recoverableOsBugDetected(), // ptr was invalid
234 else => recoverableOsBugDetected(),298 else => recoverableOsBugDetected(),
235 }299 }
...@@ -308,18 +372,25 @@ const Thread = struct {...@@ -308,18 +372,25 @@ const Thread = struct {
308 else => unreachable,372 else => unreachable,
309 };373 };
310 },374 },
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 },
312 }383 }
313 }384 }
314385
315 fn futexWake(ptr: *const u32, max_waiters: u32) void {386 fn futexWake(ptr: *const u32, max_waiters: u32) void {
316 @branchHint(.cold);387 @branchHint(.cold);
388 assert(max_waiters != 0);
317389
318 if (builtin.single_threaded) return; // nothing to wake up390 if (builtin.single_threaded) return; // nothing to wake up
319391
320 if (builtin.cpu.arch.isWasm()) {392 if (builtin.cpu.arch.isWasm()) {
321 comptime assert(builtin.cpu.has(.wasm, .atomics));393 comptime assert(builtin.cpu.has(.wasm, .atomics));
322 assert(max_waiters != 0);
323 const woken_count = asm volatile (394 const woken_count = asm volatile (
324 \\local.get %[ptr]395 \\local.get %[ptr]
325 \\local.get %[waiters]396 \\local.get %[waiters]
...@@ -364,7 +435,6 @@ const Thread = struct {...@@ -364,7 +435,6 @@ const Thread = struct {
364 }435 }
365 },436 },
366 .windows => {437 .windows => {
367 assert(max_waiters != 0);
368 switch (max_waiters) {438 switch (max_waiters) {
369 1 => windows.ntdll.RtlWakeAddressSingle(ptr),439 1 => windows.ntdll.RtlWakeAddressSingle(ptr),
370 else => windows.ntdll.RtlWakeAddressAll(ptr),440 else => windows.ntdll.RtlWakeAddressAll(ptr),
...@@ -385,7 +455,11 @@ const Thread = struct {...@@ -385,7 +455,11 @@ const Thread = struct {
385 else => unreachable, // deadlock due to operating system bug455 else => unreachable, // deadlock due to operating system bug
386 }456 }
387 },457 },
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 },
389 }463 }
390 }464 }
391};465};
...@@ -505,6 +579,47 @@ const Closure = struct {...@@ -505,6 +579,47 @@ const Closure = struct {
505 }579 }
506};580};
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
508/// Related:623/// Related:
509/// * `init_single_threaded`624/// * `init_single_threaded`
510pub fn init(625pub fn init(
...@@ -516,6 +631,7 @@ pub fn init(...@@ -516,6 +631,7 @@ pub fn init(
516 /// If these functions are avoided, then `Allocator.failing` may be passed631 /// If these functions are avoided, then `Allocator.failing` may be passed
517 /// here.632 /// here.
518 gpa: Allocator,633 gpa: Allocator,
634 options: InitOptions,
519) Threaded {635) Threaded {
520 if (builtin.single_threaded) return .init_single_threaded;636 if (builtin.single_threaded) return .init_single_threaded;
521637
...@@ -523,8 +639,9 @@ pub fn init(...@@ -523,8 +639,9 @@ pub fn init(
523639
524 var t: Threaded = .{640 var t: Threaded = .{
525 .allocator = gpa,641 .allocator = gpa,
526 .stack_size = std.Thread.SpawnConfig.default_stack_size,642 .stack_size = options.stack_size,
527 .async_limit = if (cpu_count) |n| .limited(n - 1) else |_| .nothing,643 .async_limit = options.async_limit orelse if (cpu_count) |n| .limited(n - 1) else |_| .nothing,
644 .concurrent_limit = options.concurrent_limit,
528 .cpu_count_error = if (cpu_count) |_| null else |e| e,645 .cpu_count_error = if (cpu_count) |_| null else |e| e,
529 .old_sig_io = undefined,646 .old_sig_io = undefined,
530 .old_sig_pipe = undefined,647 .old_sig_pipe = undefined,
...@@ -532,8 +649,11 @@ pub fn init(...@@ -532,8 +649,11 @@ pub fn init(
532 .main_thread = .{649 .main_thread = .{
533 .signal_id = Thread.currentSignalId(),650 .signal_id = Thread.currentSignalId(),
534 .current_closure = null,651 .current_closure = null,
535 .cancel_protection = undefined,652 .cancel_protection = .unblocked,
536 },653 },
654 .argv0 = options.argv0,
655 .environ = options.environ,
656 .robust_cancel = options.robust_cancel,
537 };657 };
538658
539 if (posix.Sigaction != void) {659 if (posix.Sigaction != void) {
...@@ -570,10 +690,26 @@ pub const init_single_threaded: Threaded = .{...@@ -570,10 +690,26 @@ pub const init_single_threaded: Threaded = .{
570 .main_thread = .{690 .main_thread = .{
571 .signal_id = undefined,691 .signal_id = undefined,
572 .current_closure = null,692 .current_closure = null,
573 .cancel_protection = undefined,693 .cancel_protection = .unblocked,
574 },694 },
695 .robust_cancel = .disabled,
696 .argv0 = .{},
697 .environ = .{},
575};698};
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
577pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {713pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {
578 t.mutex.lock();714 t.mutex.lock();
579 defer t.mutex.unlock();715 defer t.mutex.unlock();
...@@ -607,7 +743,7 @@ fn worker(t: *Threaded) void {...@@ -607,7 +743,7 @@ fn worker(t: *Threaded) void {
607 var thread: Thread = .{743 var thread: Thread = .{
608 .signal_id = Thread.currentSignalId(),744 .signal_id = Thread.currentSignalId(),
609 .current_closure = null,745 .current_closure = null,
610 .cancel_protection = undefined,746 .cancel_protection = .unblocked,
611 };747 };
612 Thread.current = &thread;748 Thread.current = &thread;
613749
...@@ -652,25 +788,64 @@ pub fn io(t: *Threaded) Io {...@@ -652,25 +788,64 @@ pub fn io(t: *Threaded) Io {
652 .futexWaitUncancelable = futexWaitUncancelable,788 .futexWaitUncancelable = futexWaitUncancelable,
653 .futexWake = futexWake,789 .futexWake = futexWake,
654790
655 .dirMake = dirMake,791 .dirCreateDir = dirCreateDir,
656 .dirMakePath = dirMakePath,792 .dirCreateDirPath = dirCreateDirPath,
657 .dirMakeOpenPath = dirMakeOpenPath,793 .dirCreateDirPathOpen = dirCreateDirPathOpen,
658 .dirStat = dirStat,794 .dirStat = dirStat,
659 .dirStatPath = dirStatPath,795 .dirStatFile = dirStatFile,
660 .fileStat = fileStat,
661 .dirAccess = dirAccess,796 .dirAccess = dirAccess,
662 .dirCreateFile = dirCreateFile,797 .dirCreateFile = dirCreateFile,
663 .dirOpenFile = dirOpenFile,798 .dirOpenFile = dirOpenFile,
664 .dirOpenDir = dirOpenDir,799 .dirOpenDir = dirOpenDir,
665 .dirClose = dirClose,800 .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,
666 .fileClose = fileClose,819 .fileClose = fileClose,
667 .fileWriteStreaming = fileWriteStreaming,820 .fileWriteStreaming = fileWriteStreaming,
668 .fileWritePositional = fileWritePositional,821 .fileWritePositional = fileWritePositional,
822 .fileWriteFileStreaming = fileWriteFileStreaming,
823 .fileWriteFilePositional = fileWriteFilePositional,
669 .fileReadStreaming = fileReadStreaming,824 .fileReadStreaming = fileReadStreaming,
670 .fileReadPositional = fileReadPositional,825 .fileReadPositional = fileReadPositional,
671 .fileSeekBy = fileSeekBy,826 .fileSeekBy = fileSeekBy,
672 .fileSeekTo = fileSeekTo,827 .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
675 .now = now,850 .now = now,
676 .sleep = sleep,851 .sleep = sleep,
...@@ -708,6 +883,7 @@ pub fn io(t: *Threaded) Io {...@@ -708,6 +883,7 @@ pub fn io(t: *Threaded) Io {
708 .windows => netWriteWindows,883 .windows => netWriteWindows,
709 else => netWritePosix,884 else => netWritePosix,
710 },885 },
886 .netWriteFile = netWriteFile,
711 .netSend = switch (native_os) {887 .netSend = switch (native_os) {
712 .windows => netSendWindows,888 .windows => netSendWindows,
713 else => netSendPosix,889 else => netSendPosix,
...@@ -748,25 +924,64 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -748,25 +924,64 @@ pub fn ioBasic(t: *Threaded) Io {
748 .futexWaitUncancelable = futexWaitUncancelable,924 .futexWaitUncancelable = futexWaitUncancelable,
749 .futexWake = futexWake,925 .futexWake = futexWake,
750926
751 .dirMake = dirMake,927 .dirCreateDir = dirCreateDir,
752 .dirMakePath = dirMakePath,928 .dirCreateDirPath = dirCreateDirPath,
753 .dirMakeOpenPath = dirMakeOpenPath,929 .dirCreateDirPathOpen = dirCreateDirPathOpen,
754 .dirStat = dirStat,930 .dirStat = dirStat,
755 .dirStatPath = dirStatPath,931 .dirStatFile = dirStatFile,
756 .fileStat = fileStat,
757 .dirAccess = dirAccess,932 .dirAccess = dirAccess,
758 .dirCreateFile = dirCreateFile,933 .dirCreateFile = dirCreateFile,
759 .dirOpenFile = dirOpenFile,934 .dirOpenFile = dirOpenFile,
760 .dirOpenDir = dirOpenDir,935 .dirOpenDir = dirOpenDir,
761 .dirClose = dirClose,936 .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,
762 .fileClose = fileClose,955 .fileClose = fileClose,
763 .fileWriteStreaming = fileWriteStreaming,956 .fileWriteStreaming = fileWriteStreaming,
764 .fileWritePositional = fileWritePositional,957 .fileWritePositional = fileWritePositional,
958 .fileWriteFileStreaming = fileWriteFileStreaming,
959 .fileWriteFilePositional = fileWriteFilePositional,
765 .fileReadStreaming = fileReadStreaming,960 .fileReadStreaming = fileReadStreaming,
766 .fileReadPositional = fileReadPositional,961 .fileReadPositional = fileReadPositional,
767 .fileSeekBy = fileSeekBy,962 .fileSeekBy = fileSeekBy,
768 .fileSeekTo = fileSeekTo,963 .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
771 .now = now,986 .now = now,
772 .sleep = sleep,987 .sleep = sleep,
...@@ -780,6 +995,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -780,6 +995,7 @@ pub fn ioBasic(t: *Threaded) Io {
780 .netClose = netCloseUnavailable,995 .netClose = netCloseUnavailable,
781 .netRead = netReadUnavailable,996 .netRead = netReadUnavailable,
782 .netWrite = netWriteUnavailable,997 .netWrite = netWriteUnavailable,
998 .netWriteFile = netWriteFileUnavailable,
783 .netSend = netSendUnavailable,999 .netSend = netSendUnavailable,
784 .netReceive = netReceiveUnavailable,1000 .netReceive = netReceiveUnavailable,
785 .netInterfaceNameResolve = netInterfaceNameResolveUnavailable,1001 .netInterfaceNameResolve = netInterfaceNameResolveUnavailable,
...@@ -789,7 +1005,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -789,7 +1005,7 @@ pub fn ioBasic(t: *Threaded) Io {
789 };1005 };
790}1006}
7911007
792pub const socket_flags_unsupported = native_os.isDarwin() or native_os == .haiku;1008pub const socket_flags_unsupported = is_darwin or native_os == .haiku;
793const have_accept4 = !socket_flags_unsupported;1009const have_accept4 = !socket_flags_unsupported;
794const have_flock_open_flags = @hasField(posix.O, "EXLOCK");1010const have_flock_open_flags = @hasField(posix.O, "EXLOCK");
795const have_networking = native_os != .wasi;1011const have_networking = native_os != .wasi;
...@@ -805,12 +1021,47 @@ const have_preadv = switch (native_os) {...@@ -805,12 +1021,47 @@ const have_preadv = switch (native_os) {
805};1021};
806const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO");1022const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO");
807const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE");1023const 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
809const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;1047const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
810const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;1048const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
811const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;1049const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
812const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;1050const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
813const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;1051const 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
815/// Trailing data:1066/// Trailing data:
816/// 1. context1067/// 1. context
...@@ -1018,7 +1269,6 @@ const GroupClosure = struct {...@@ -1018,7 +1269,6 @@ const GroupClosure = struct {
1018 const group = gc.group;1269 const group = gc.group;
1019 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);1270 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
1020 const event: *Io.Event = @ptrCast(&group.context);1271 const event: *Io.Event = @ptrCast(&group.context);
1021
1022 current_thread.current_closure = closure;1272 current_thread.current_closure = closure;
1023 current_thread.cancel_protection = .unblocked;1273 current_thread.cancel_protection = .unblocked;
10241274
...@@ -1304,6 +1554,7 @@ fn cancel(...@@ -1304,6 +1554,7 @@ fn cancel(
1304}1554}
13051555
1306fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void {1556fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void {
1557 if (builtin.single_threaded) unreachable; // Deadlock.
1307 const t: *Threaded = @ptrCast(@alignCast(userdata));1558 const t: *Threaded = @ptrCast(@alignCast(userdata));
1308 const current_thread = Thread.getCurrent(t);1559 const current_thread = Thread.getCurrent(t);
1309 const t_io = ioBasic(t);1560 const t_io = ioBasic(t);
...@@ -1311,37 +1562,30 @@ fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io....@@ -1311,37 +1562,30 @@ fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.
1311 const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null;1562 const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null;
1312 break :ns std.math.lossyCast(u64, d.raw.toNanoseconds());1563 break :ns std.math.lossyCast(u64, d.raw.toNanoseconds());
1313 };1564 };
1314 switch (native_os) {1565 return Thread.futexWaitTimed(current_thread, ptr, expected, timeout_ns);
1315 .illumos, .netbsd, .openbsd => @panic("TODO"),
1316 else => try current_thread.futexWaitTimed(ptr, expected, timeout_ns),
1317 }
1318}1566}
13191567
1320fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {1568fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {
1569 if (builtin.single_threaded) unreachable; // Deadlock.
1321 const t: *Threaded = @ptrCast(@alignCast(userdata));1570 const t: *Threaded = @ptrCast(@alignCast(userdata));
1322 _ = t;1571 _ = t;
1323 switch (native_os) {1572 Thread.futexWaitUncancelable(ptr, expected);
1324 .illumos, .netbsd, .openbsd => @panic("TODO"),
1325 else => Thread.futexWaitUncancelable(ptr, expected),
1326 }
1327}1573}
13281574
1329fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {1575fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
1576 if (builtin.single_threaded) unreachable; // Nothing to wake up.
1330 const t: *Threaded = @ptrCast(@alignCast(userdata));1577 const t: *Threaded = @ptrCast(@alignCast(userdata));
1331 _ = t;1578 _ = t;
1332 switch (native_os) {1579 Thread.futexWake(ptr, max_waiters);
1333 .illumos, .netbsd, .openbsd => @panic("TODO"),
1334 else => Thread.futexWake(ptr, max_waiters),
1335 }
1336}1580}
13371581
1338const dirMake = switch (native_os) {1582const dirCreateDir = switch (native_os) {
1339 .windows => dirMakeWindows,1583 .windows => dirCreateDirWindows,
1340 .wasi => dirMakeWasi,1584 .wasi => dirCreateDirWasi,
1341 else => dirMakePosix,1585 else => dirCreateDirPosix,
1342};1586};
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 {
1345 const t: *Threaded = @ptrCast(@alignCast(userdata));1589 const t: *Threaded = @ptrCast(@alignCast(userdata));
1346 const current_thread = Thread.getCurrent(t);1590 const current_thread = Thread.getCurrent(t);
13471591
...@@ -1350,7 +1594,7 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:...@@ -1350,7 +1594,7 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
13501594
1351 try current_thread.beginSyscall();1595 try current_thread.beginSyscall();
1352 while (true) {1596 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()))) {
1354 .SUCCESS => {1598 .SUCCESS => {
1355 current_thread.endSyscall();1599 current_thread.endSyscall();
1356 return;1600 return;
...@@ -1359,7 +1603,6 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:...@@ -1359,7 +1603,6 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
1359 try current_thread.checkCancel();1603 try current_thread.checkCancel();
1360 continue;1604 continue;
1361 },1605 },
1362 .CANCELED => return current_thread.endSyscallCanceled(),
1363 else => |e| {1606 else => |e| {
1364 current_thread.endSyscall();1607 current_thread.endSyscall();
1365 switch (e) {1608 switch (e) {
...@@ -1387,8 +1630,8 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:...@@ -1387,8 +1630,8 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
1387 }1630 }
1388}1631}
13891632
1390fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {1633fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
1391 if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, mode);1634 if (builtin.link_libc) return dirCreateDirPosix(userdata, dir, sub_path, permissions);
1392 const t: *Threaded = @ptrCast(@alignCast(userdata));1635 const t: *Threaded = @ptrCast(@alignCast(userdata));
1393 const current_thread = Thread.getCurrent(t);1636 const current_thread = Thread.getCurrent(t);
1394 try current_thread.beginSyscall();1637 try current_thread.beginSyscall();
...@@ -1402,7 +1645,6 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I...@@ -1402,7 +1645,6 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I
1402 try current_thread.checkCancel();1645 try current_thread.checkCancel();
1403 continue;1646 continue;
1404 },1647 },
1405 .CANCELED => return current_thread.endSyscallCanceled(),
1406 else => |e| {1648 else => |e| {
1407 current_thread.endSyscall();1649 current_thread.endSyscall();
1408 switch (e) {1650 switch (e) {
...@@ -1429,13 +1671,13 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I...@@ -1429,13 +1671,13 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I
1429 }1671 }
1430}1672}
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 {
1433 const t: *Threaded = @ptrCast(@alignCast(userdata));1675 const t: *Threaded = @ptrCast(@alignCast(userdata));
1434 const current_thread = Thread.getCurrent(t);1676 const current_thread = Thread.getCurrent(t);
1435 try current_thread.checkCancel();1677 try current_thread.checkCancel();
14361678
1437 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);1679 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
1438 _ = mode;1680 _ = permissions; // TODO use this value
1439 const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{1681 const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{
1440 .dir = dir.handle,1682 .dir = dir.handle,
1441 .access_mask = .{1683 .access_mask = .{
...@@ -1455,62 +1697,75 @@ fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode...@@ -1455,62 +1697,75 @@ fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode
1455 windows.CloseHandle(sub_dir_handle);1697 windows.CloseHandle(sub_dir_handle);
1456}1698}
14571699
1458const dirMakePath = switch (native_os) {1700fn dirCreateDirPath(
1459 .windows => dirMakePathWindows,1701 userdata: ?*anyopaque,
1460 else => dirMakePathPosix,1702 dir: Dir,
1461};1703 sub_path: []const u8,
14621704 permissions: Dir.Permissions,
1463fn dirMakePathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {1705) Dir.CreateDirPathError!Dir.CreatePathStatus {
1464 const t: *Threaded = @ptrCast(@alignCast(userdata));1706 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 {1708 var it = std.fs.path.componentIterator(sub_path);
1473 const t: *Threaded = @ptrCast(@alignCast(userdata));1709 var status: Dir.CreatePathStatus = .existed;
1474 _ = t;1710 var component = it.last() orelse return error.BadPathName;
1475 _ = dir;1711 while (true) {
1476 _ = sub_path;1712 if (dirCreateDir(t, dir, component.path, permissions)) |_| {
1477 _ = mode;1713 status = .created;
1478 @panic("TODO implement dirMakePathWindows");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 }
1479}1730}
14801731
1481const dirMakeOpenPath = switch (native_os) {1732const dirCreateDirPathOpen = switch (native_os) {
1482 .windows => dirMakeOpenPathWindows,1733 .windows => dirCreateDirPathOpenWindows,
1483 .wasi => dirMakeOpenPathWasi,1734 .wasi => dirCreateDirPathOpenWasi,
1484 else => dirMakeOpenPathPosix,1735 else => dirCreateDirPathOpenPosix,
1485};1736};
14861737
1487fn dirMakeOpenPathPosix(1738fn dirCreateDirPathOpenPosix(
1488 userdata: ?*anyopaque,1739 userdata: ?*anyopaque,
1489 dir: Io.Dir,1740 dir: Dir,
1490 sub_path: []const u8,1741 sub_path: []const u8,
1491 options: Io.Dir.OpenOptions,1742 permissions: Dir.Permissions,
1492) Io.Dir.MakeOpenPathError!Io.Dir {1743 options: Dir.OpenOptions,
1744) Dir.CreateDirPathOpenError!Dir {
1493 const t: *Threaded = @ptrCast(@alignCast(userdata));1745 const t: *Threaded = @ptrCast(@alignCast(userdata));
1494 const t_io = ioBasic(t);1746 const t_io = ioBasic(t);
1495 return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) {1747 return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) {
1496 error.FileNotFound => {1748 error.FileNotFound => {
1497 try dir.makePath(t_io, sub_path);1749 _ = try dir.createDirPathStatus(t_io, sub_path, permissions);
1498 return dirOpenDirPosix(t, dir, sub_path, options);1750 return dirOpenDirPosix(t, dir, sub_path, options);
1499 },1751 },
1500 else => |e| return e,1752 else => |e| return e,
1501 };1753 };
1502}1754}
15031755
1504fn dirMakeOpenPathWindows(1756fn dirCreateDirPathOpenWindows(
1505 userdata: ?*anyopaque,1757 userdata: ?*anyopaque,
1506 dir: Io.Dir,1758 dir: Dir,
1507 sub_path: []const u8,1759 sub_path: []const u8,
1508 options: Io.Dir.OpenOptions,1760 permissions: Dir.Permissions,
1509) Io.Dir.MakeOpenPathError!Io.Dir {1761 options: Dir.OpenOptions,
1762) Dir.CreateDirPathOpenError!Dir {
1510 const t: *Threaded = @ptrCast(@alignCast(userdata));1763 const t: *Threaded = @ptrCast(@alignCast(userdata));
1511 const current_thread = Thread.getCurrent(t);1764 const current_thread = Thread.getCurrent(t);
1512 const w = windows;1765 const w = windows;
15131766
1767 _ = permissions; // TODO apply these permissions
1768
1514 var it = std.fs.path.componentIterator(sub_path);1769 var it = std.fs.path.componentIterator(sub_path);
1515 // If there are no components in the path, then create a dummy component with the full path.1770 // If there are no components in the path, then create a dummy component with the full path.
1516 var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{1771 var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{
...@@ -1526,7 +1781,7 @@ fn dirMakeOpenPathWindows(...@@ -1526,7 +1781,7 @@ fn dirMakeOpenPathWindows(
1526 const is_last = it.peekNext() == null;1781 const is_last = it.peekNext() == null;
1527 const create_disposition: w.FILE.CREATE_DISPOSITION = if (is_last) .OPEN_IF else .CREATE;1782 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
1531 const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);1786 const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);
1532 var nt_name: w.UNICODE_STRING = .{1787 var nt_name: w.UNICODE_STRING = .{
...@@ -1584,16 +1839,10 @@ fn dirMakeOpenPathWindows(...@@ -1584,16 +1839,10 @@ fn dirMakeOpenPathWindows(
1584 // stat the file and return an error if it's not a directory1839 // stat the file and return an error if it's not a directory
1585 // this is important because otherwise a dangling symlink1840 // this is important because otherwise a dangling symlink
1586 // could cause an infinite loop1841 // could cause an infinite loop
1587 check_dir: {1842 const fstat = try dirStatFileWindows(t, dir, component.path, .{
1588 // workaround for windows, see https://github.com/ziglang/zig/issues/167381843 .follow_symlinks = options.follow_symlinks,
1589 const fstat = dirStatPathWindows(t, dir, component.path, .{1844 });
1590 .follow_symlinks = options.follow_symlinks,1845 if (fstat.kind != .directory) return error.NotDir;
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 }
15971846
1598 component = it.next().?;1847 component = it.next().?;
1599 continue;1848 continue;
...@@ -1616,46 +1865,51 @@ fn dirMakeOpenPathWindows(...@@ -1616,46 +1865,51 @@ fn dirMakeOpenPathWindows(
1616 }1865 }
1617}1866}
16181867
1619fn dirMakeOpenPathWasi(1868fn dirCreateDirPathOpenWasi(
1620 userdata: ?*anyopaque,1869 userdata: ?*anyopaque,
1621 dir: Io.Dir,1870 dir: Dir,
1622 sub_path: []const u8,1871 sub_path: []const u8,
1623 options: Io.Dir.OpenOptions,1872 permissions: Dir.Permissions,
1624) Io.Dir.MakeOpenPathError!Io.Dir {1873 options: Dir.OpenOptions,
1874) Dir.CreateDirPathOpenError!Dir {
1625 const t: *Threaded = @ptrCast(@alignCast(userdata));1875 const t: *Threaded = @ptrCast(@alignCast(userdata));
1626 const t_io = ioBasic(t);1876 const t_io = ioBasic(t);
1627 return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) {1877 return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) {
1628 error.FileNotFound => {1878 error.FileNotFound => {
1629 try dir.makePath(t_io, sub_path);1879 _ = try dir.createDirPathStatus(t_io, sub_path, permissions);
1630 return dirOpenDirWasi(t, dir, sub_path, options);1880 return dirOpenDirWasi(t, dir, sub_path, options);
1631 },1881 },
1632 else => |e| return e,1882 else => |e| return e,
1633 };1883 };
1634}1884}
16351885
1636fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {1886fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
1637 const t: *Threaded = @ptrCast(@alignCast(userdata));1887 const t: *Threaded = @ptrCast(@alignCast(userdata));
1638 _ = t;1888 const file: File = .{ .handle = dir.handle };
1639 _ = dir;1889 return fileStat(t, file);
1640 @panic("TODO implement dirStat");
1641}1890}
16421891
1643const dirStatPath = switch (native_os) {1892const dirStatFile = switch (native_os) {
1644 .linux => dirStatPathLinux,1893 .linux => dirStatFileLinux,
1645 .windows => dirStatPathWindows,1894 .windows => dirStatFileWindows,
1646 .wasi => dirStatPathWasi,1895 .wasi => dirStatFileWasi,
1647 else => dirStatPathPosix,1896 else => dirStatFilePosix,
1648};1897};
16491898
1650fn dirStatPathLinux(1899fn dirStatFileLinux(
1651 userdata: ?*anyopaque,1900 userdata: ?*anyopaque,
1652 dir: Io.Dir,1901 dir: Dir,
1653 sub_path: []const u8,1902 sub_path: []const u8,
1654 options: Io.Dir.StatPathOptions,1903 options: Dir.StatFileOptions,
1655) Io.Dir.StatPathError!Io.File.Stat {1904) Dir.StatFileError!File.Stat {
1656 const t: *Threaded = @ptrCast(@alignCast(userdata));1905 const t: *Threaded = @ptrCast(@alignCast(userdata));
1657 const current_thread = Thread.getCurrent(t);1906 const current_thread = Thread.getCurrent(t);
1658 const linux = std.os.linux;1907 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
1660 var path_buffer: [posix.PATH_MAX]u8 = undefined;1914 var path_buffer: [posix.PATH_MAX]u8 = undefined;
1661 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);1915 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
...@@ -1666,30 +1920,15 @@ fn dirStatPathLinux(...@@ -1666,30 +1920,15 @@ fn dirStatPathLinux(
1666 try current_thread.beginSyscall();1920 try current_thread.beginSyscall();
1667 while (true) {1921 while (true) {
1668 var statx = std.mem.zeroes(linux.Statx);1922 var statx = std.mem.zeroes(linux.Statx);
1669 const rc = linux.statx(1923 switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_mask, &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)) {
1677 .SUCCESS => {1924 .SUCCESS => {
1678 current_thread.endSyscall();1925 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);
1686 return statFromLinux(&statx);1926 return statFromLinux(&statx);
1687 },1927 },
1688 .INTR => {1928 .INTR => {
1689 try current_thread.checkCancel();1929 try current_thread.checkCancel();
1690 continue;1930 continue;
1691 },1931 },
1692 .CANCELED => return current_thread.endSyscallCanceled(),
1693 else => |e| {1932 else => |e| {
1694 current_thread.endSyscall();1933 current_thread.endSyscall();
1695 switch (e) {1934 switch (e) {
...@@ -1709,12 +1948,12 @@ fn dirStatPathLinux(...@@ -1709,12 +1948,12 @@ fn dirStatPathLinux(
1709 }1948 }
1710}1949}
17111950
1712fn dirStatPathPosix(1951fn dirStatFilePosix(
1713 userdata: ?*anyopaque,1952 userdata: ?*anyopaque,
1714 dir: Io.Dir,1953 dir: Dir,
1715 sub_path: []const u8,1954 sub_path: []const u8,
1716 options: Io.Dir.StatPathOptions,1955 options: Dir.StatFileOptions,
1717) Io.Dir.StatPathError!Io.File.Stat {1956) Dir.StatFileError!File.Stat {
1718 const t: *Threaded = @ptrCast(@alignCast(userdata));1957 const t: *Threaded = @ptrCast(@alignCast(userdata));
1719 const current_thread = Thread.getCurrent(t);1958 const current_thread = Thread.getCurrent(t);
17201959
...@@ -1723,10 +1962,14 @@ fn dirStatPathPosix(...@@ -1723,10 +1962,14 @@ fn dirStatPathPosix(
17231962
1724 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;1963 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 {
1726 try current_thread.beginSyscall();1969 try current_thread.beginSyscall();
1727 while (true) {1970 while (true) {
1728 var stat = std.mem.zeroes(posix.Stat);1971 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))) {
1730 .SUCCESS => {1973 .SUCCESS => {
1731 current_thread.endSyscall();1974 current_thread.endSyscall();
1732 return statFromPosix(&stat);1975 return statFromPosix(&stat);
...@@ -1735,7 +1978,6 @@ fn dirStatPathPosix(...@@ -1735,7 +1978,6 @@ fn dirStatPathPosix(
1735 try current_thread.checkCancel();1978 try current_thread.checkCancel();
1736 continue;1979 continue;
1737 },1980 },
1738 .CANCELED => return current_thread.endSyscallCanceled(),
1739 else => |e| {1981 else => |e| {
1740 current_thread.endSyscall();1982 current_thread.endSyscall();
1741 switch (e) {1983 switch (e) {
...@@ -1757,12 +1999,12 @@ fn dirStatPathPosix(...@@ -1757,12 +1999,12 @@ fn dirStatPathPosix(
1757 }1999 }
1758}2000}
17592001
1760fn dirStatPathWindows(2002fn dirStatFileWindows(
1761 userdata: ?*anyopaque,2003 userdata: ?*anyopaque,
1762 dir: Io.Dir,2004 dir: Dir,
1763 sub_path: []const u8,2005 sub_path: []const u8,
1764 options: Io.Dir.StatPathOptions,2006 options: Dir.StatFileOptions,
1765) Io.Dir.StatPathError!Io.File.Stat {2007) Dir.StatFileError!File.Stat {
1766 const t: *Threaded = @ptrCast(@alignCast(userdata));2008 const t: *Threaded = @ptrCast(@alignCast(userdata));
1767 const file = try dirOpenFileWindows(t, dir, sub_path, .{2009 const file = try dirOpenFileWindows(t, dir, sub_path, .{
1768 .follow_symlinks = options.follow_symlinks,2010 .follow_symlinks = options.follow_symlinks,
...@@ -1771,13 +2013,13 @@ fn dirStatPathWindows(...@@ -1771,13 +2013,13 @@ fn dirStatPathWindows(
1771 return fileStatWindows(t, file);2013 return fileStatWindows(t, file);
1772}2014}
17732015
1774fn dirStatPathWasi(2016fn dirStatFileWasi(
1775 userdata: ?*anyopaque,2017 userdata: ?*anyopaque,
1776 dir: Io.Dir,2018 dir: Dir,
1777 sub_path: []const u8,2019 sub_path: []const u8,
1778 options: Io.Dir.StatPathOptions,2020 options: Dir.StatFileOptions,
1779) Io.Dir.StatPathError!Io.File.Stat {2021) Dir.StatFileError!File.Stat {
1780 if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options);2022 if (builtin.link_libc) return dirStatFilePosix(userdata, dir, sub_path, options);
1781 const t: *Threaded = @ptrCast(@alignCast(userdata));2023 const t: *Threaded = @ptrCast(@alignCast(userdata));
1782 const current_thread = Thread.getCurrent(t);2024 const current_thread = Thread.getCurrent(t);
1783 const wasi = std.os.wasi;2025 const wasi = std.os.wasi;
...@@ -1796,7 +2038,6 @@ fn dirStatPathWasi(...@@ -1796,7 +2038,6 @@ fn dirStatPathWasi(
1796 try current_thread.checkCancel();2038 try current_thread.checkCancel();
1797 continue;2039 continue;
1798 },2040 },
1799 .CANCELED => return current_thread.endSyscallCanceled(),
1800 else => |e| {2041 else => |e| {
1801 current_thread.endSyscall();2042 current_thread.endSyscall();
1802 switch (e) {2043 switch (e) {
...@@ -1817,6 +2058,51 @@ fn dirStatPathWasi(...@@ -1817,6 +2058,51 @@ fn dirStatPathWasi(
1817 }2058 }
1818}2059}
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
1820const fileStat = switch (native_os) {2106const fileStat = switch (native_os) {
1821 .linux => fileStatLinux,2107 .linux => fileStatLinux,
1822 .windows => fileStatWindows,2108 .windows => fileStatWindows,
...@@ -1824,7 +2110,7 @@ const fileStat = switch (native_os) {...@@ -1824,7 +2110,7 @@ const fileStat = switch (native_os) {
1824 else => fileStatPosix,2110 else => fileStatPosix,
1825};2111};
18262112
1827fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {2113fn fileStatPosix(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
1828 const t: *Threaded = @ptrCast(@alignCast(userdata));2114 const t: *Threaded = @ptrCast(@alignCast(userdata));
1829 const current_thread = Thread.getCurrent(t);2115 const current_thread = Thread.getCurrent(t);
18302116
...@@ -1842,7 +2128,6 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File...@@ -1842,7 +2128,6 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File
1842 try current_thread.checkCancel();2128 try current_thread.checkCancel();
1843 continue;2129 continue;
1844 },2130 },
1845 .CANCELED => return current_thread.endSyscallCanceled(),
1846 else => |e| {2131 else => |e| {
1847 current_thread.endSyscall();2132 current_thread.endSyscall();
1848 switch (e) {2133 switch (e) {
...@@ -1857,38 +2142,28 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File...@@ -1857,38 +2142,28 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File
1857 }2142 }
1858}2143}
18592144
1860fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {2145fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
1861 const t: *Threaded = @ptrCast(@alignCast(userdata));2146 const t: *Threaded = @ptrCast(@alignCast(userdata));
1862 const current_thread = Thread.getCurrent(t);2147 const current_thread = Thread.getCurrent(t);
1863 const linux = std.os.linux;2148 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
1865 try current_thread.beginSyscall();2155 try current_thread.beginSyscall();
1866 while (true) {2156 while (true) {
1867 var statx = std.mem.zeroes(linux.Statx);2157 var statx = std.mem.zeroes(linux.Statx);
1868 const rc = linux.statx(2158 switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_mask, &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)) {
1876 .SUCCESS => {2159 .SUCCESS => {
1877 current_thread.endSyscall();2160 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);
1885 return statFromLinux(&statx);2161 return statFromLinux(&statx);
1886 },2162 },
1887 .INTR => {2163 .INTR => {
1888 try current_thread.checkCancel();2164 try current_thread.checkCancel();
1889 continue;2165 continue;
1890 },2166 },
1891 .CANCELED => return current_thread.endSyscallCanceled(),
1892 else => |e| {2167 else => |e| {
1893 current_thread.endSyscall();2168 current_thread.endSyscall();
1894 switch (e) {2169 switch (e) {
...@@ -1908,7 +2183,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File...@@ -1908,7 +2183,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File
1908 }2183 }
1909}2184}
19102185
1911fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {2186fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
1912 const t: *Threaded = @ptrCast(@alignCast(userdata));2187 const t: *Threaded = @ptrCast(@alignCast(userdata));
1913 const current_thread = Thread.getCurrent(t);2188 const current_thread = Thread.getCurrent(t);
1914 try current_thread.checkCancel();2189 try current_thread.checkCancel();
...@@ -1922,14 +2197,14 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi...@@ -1922,14 +2197,14 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi
1922 // size provided. This is treated as success because the type of variable-length information that this would be relevant for2197 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
1923 // (name, volume name, etc) we don't care about.2198 // (name, volume name, etc) we don't care about.
1924 .BUFFER_OVERFLOW => {},2199 .BUFFER_OVERFLOW => {},
1925 .INVALID_PARAMETER => unreachable,2200 .INVALID_PARAMETER => |err| return windows.statusBug(err),
1926 .ACCESS_DENIED => return error.AccessDenied,2201 .ACCESS_DENIED => return error.AccessDenied,
1927 else => return windows.unexpectedStatus(rc),2202 else => return windows.unexpectedStatus(rc),
1928 }2203 }
1929 return .{2204 return .{
1930 .inode = info.InternalInformation.IndexNumber,2205 .inode = info.InternalInformation.IndexNumber,
1931 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),2206 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
1932 .mode = 0,2207 .permissions = .default_file,
1933 .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: {2208 .kind = if (info.BasicInformation.FileAttributes.REPARSE_POINT) reparse_point: {
1934 var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined;2209 var tag_info: windows.FILE.ATTRIBUTE_TAG_INFO = undefined;
1935 const tag_rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE.ATTRIBUTE_TAG_INFO), .AttributeTag);2210 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...@@ -1937,7 +2212,7 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi
1937 .SUCCESS => {},2212 .SUCCESS => {},
1938 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors2213 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
1939 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e2214 // 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),
1941 .ACCESS_DENIED => return error.AccessDenied,2216 .ACCESS_DENIED => return error.AccessDenied,
1942 else => return windows.unexpectedStatus(rc),2217 else => return windows.unexpectedStatus(rc),
1943 }2218 }
...@@ -1951,10 +2226,11 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi...@@ -1951,10 +2226,11 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi
1951 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),2226 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
1952 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),2227 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
1953 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),2228 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
2229 .nlink = 0,
1954 };2230 };
1955}2231}
19562232
1957fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {2233fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
1958 if (builtin.link_libc) return fileStatPosix(userdata, file);2234 if (builtin.link_libc) return fileStatPosix(userdata, file);
19592235
1960 const t: *Threaded = @ptrCast(@alignCast(userdata));2236 const t: *Threaded = @ptrCast(@alignCast(userdata));
...@@ -1972,7 +2248,6 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File....@@ -1972,7 +2248,6 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.
1972 try current_thread.checkCancel();2248 try current_thread.checkCancel();
1973 continue;2249 continue;
1974 },2250 },
1975 .CANCELED => return current_thread.endSyscallCanceled(),
1976 else => |e| {2251 else => |e| {
1977 current_thread.endSyscall();2252 current_thread.endSyscall();
1978 switch (e) {2253 switch (e) {
...@@ -1996,10 +2271,10 @@ const dirAccess = switch (native_os) {...@@ -1996,10 +2271,10 @@ const dirAccess = switch (native_os) {
19962271
1997fn dirAccessPosix(2272fn dirAccessPosix(
1998 userdata: ?*anyopaque,2273 userdata: ?*anyopaque,
1999 dir: Io.Dir,2274 dir: Dir,
2000 sub_path: []const u8,2275 sub_path: []const u8,
2001 options: Io.Dir.AccessOptions,2276 options: Dir.AccessOptions,
2002) Io.Dir.AccessError!void {2277) Dir.AccessError!void {
2003 const t: *Threaded = @ptrCast(@alignCast(userdata));2278 const t: *Threaded = @ptrCast(@alignCast(userdata));
2004 const current_thread = Thread.getCurrent(t);2279 const current_thread = Thread.getCurrent(t);
20052280
...@@ -2024,7 +2299,6 @@ fn dirAccessPosix(...@@ -2024,7 +2299,6 @@ fn dirAccessPosix(
2024 try current_thread.checkCancel();2299 try current_thread.checkCancel();
2025 continue;2300 continue;
2026 },2301 },
2027 .CANCELED => return current_thread.endSyscallCanceled(),
2028 else => |e| {2302 else => |e| {
2029 current_thread.endSyscall();2303 current_thread.endSyscall();
2030 switch (e) {2304 switch (e) {
...@@ -2050,10 +2324,10 @@ fn dirAccessPosix(...@@ -2050,10 +2324,10 @@ fn dirAccessPosix(
20502324
2051fn dirAccessWasi(2325fn dirAccessWasi(
2052 userdata: ?*anyopaque,2326 userdata: ?*anyopaque,
2053 dir: Io.Dir,2327 dir: Dir,
2054 sub_path: []const u8,2328 sub_path: []const u8,
2055 options: Io.Dir.AccessOptions,2329 options: Dir.AccessOptions,
2056) Io.Dir.AccessError!void {2330) Dir.AccessError!void {
2057 if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options);2331 if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options);
2058 const t: *Threaded = @ptrCast(@alignCast(userdata));2332 const t: *Threaded = @ptrCast(@alignCast(userdata));
2059 const current_thread = Thread.getCurrent(t);2333 const current_thread = Thread.getCurrent(t);
...@@ -2074,7 +2348,6 @@ fn dirAccessWasi(...@@ -2074,7 +2348,6 @@ fn dirAccessWasi(
2074 try current_thread.checkCancel();2348 try current_thread.checkCancel();
2075 continue;2349 continue;
2076 },2350 },
2077 .CANCELED => return current_thread.endSyscallCanceled(),
2078 else => |e| {2351 else => |e| {
2079 current_thread.endSyscall();2352 current_thread.endSyscall();
2080 switch (e) {2353 switch (e) {
...@@ -2123,10 +2396,10 @@ fn dirAccessWasi(...@@ -2123,10 +2396,10 @@ fn dirAccessWasi(
21232396
2124fn dirAccessWindows(2397fn dirAccessWindows(
2125 userdata: ?*anyopaque,2398 userdata: ?*anyopaque,
2126 dir: Io.Dir,2399 dir: Dir,
2127 sub_path: []const u8,2400 sub_path: []const u8,
2128 options: Io.Dir.AccessOptions,2401 options: Dir.AccessOptions,
2129) Io.Dir.AccessError!void {2402) Dir.AccessError!void {
2130 const t: *Threaded = @ptrCast(@alignCast(userdata));2403 const t: *Threaded = @ptrCast(@alignCast(userdata));
2131 const current_thread = Thread.getCurrent(t);2404 const current_thread = Thread.getCurrent(t);
2132 try current_thread.checkCancel();2405 try current_thread.checkCancel();
...@@ -2175,10 +2448,10 @@ const dirCreateFile = switch (native_os) {...@@ -2175,10 +2448,10 @@ const dirCreateFile = switch (native_os) {
21752448
2176fn dirCreateFilePosix(2449fn dirCreateFilePosix(
2177 userdata: ?*anyopaque,2450 userdata: ?*anyopaque,
2178 dir: Io.Dir,2451 dir: Dir,
2179 sub_path: []const u8,2452 sub_path: []const u8,
2180 flags: Io.File.CreateFlags,2453 flags: File.CreateFlags,
2181) Io.File.OpenError!Io.File {2454) File.OpenError!File {
2182 const t: *Threaded = @ptrCast(@alignCast(userdata));2455 const t: *Threaded = @ptrCast(@alignCast(userdata));
2183 const current_thread = Thread.getCurrent(t);2456 const current_thread = Thread.getCurrent(t);
21842457
...@@ -2211,7 +2484,7 @@ fn dirCreateFilePosix(...@@ -2211,7 +2484,7 @@ fn dirCreateFilePosix(
22112484
2212 try current_thread.beginSyscall();2485 try current_thread.beginSyscall();
2213 const fd: posix.fd_t = while (true) {2486 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());
2215 switch (posix.errno(rc)) {2488 switch (posix.errno(rc)) {
2216 .SUCCESS => {2489 .SUCCESS => {
2217 current_thread.endSyscall();2490 current_thread.endSyscall();
...@@ -2221,7 +2494,6 @@ fn dirCreateFilePosix(...@@ -2221,7 +2494,6 @@ fn dirCreateFilePosix(
2221 try current_thread.checkCancel();2494 try current_thread.checkCancel();
2222 continue;2495 continue;
2223 },2496 },
2224 .CANCELED => return current_thread.endSyscallCanceled(),
2225 else => |e| {2497 else => |e| {
2226 current_thread.endSyscall();2498 current_thread.endSyscall();
2227 switch (e) {2499 switch (e) {
...@@ -2238,14 +2510,14 @@ fn dirCreateFilePosix(...@@ -2238,14 +2510,14 @@ fn dirCreateFilePosix(
2238 .NFILE => return error.SystemFdQuotaExceeded,2510 .NFILE => return error.SystemFdQuotaExceeded,
2239 .NODEV => return error.NoDevice,2511 .NODEV => return error.NoDevice,
2240 .NOENT => return error.FileNotFound,2512 .NOENT => return error.FileNotFound,
2241 .SRCH => return error.ProcessNotFound,2513 .SRCH => return error.FileNotFound, // Linux when accessing procfs.
2242 .NOMEM => return error.SystemResources,2514 .NOMEM => return error.SystemResources,
2243 .NOSPC => return error.NoSpaceLeft,2515 .NOSPC => return error.NoSpaceLeft,
2244 .NOTDIR => return error.NotDir,2516 .NOTDIR => return error.NotDir,
2245 .PERM => return error.PermissionDenied,2517 .PERM => return error.PermissionDenied,
2246 .EXIST => return error.PathAlreadyExists,2518 .EXIST => return error.PathAlreadyExists,
2247 .BUSY => return error.DeviceBusy,2519 .BUSY => return error.DeviceBusy,
2248 .OPNOTSUPP => return error.FileLocksNotSupported,2520 .OPNOTSUPP => return error.FileLocksUnsupported,
2249 .AGAIN => return error.WouldBlock,2521 .AGAIN => return error.WouldBlock,
2250 .TXTBSY => return error.FileBusy,2522 .TXTBSY => return error.FileBusy,
2251 .NXIO => return error.NoDevice,2523 .NXIO => return error.NoDevice,
...@@ -2276,7 +2548,6 @@ fn dirCreateFilePosix(...@@ -2276,7 +2548,6 @@ fn dirCreateFilePosix(
2276 try current_thread.checkCancel();2548 try current_thread.checkCancel();
2277 continue;2549 continue;
2278 },2550 },
2279 .CANCELED => return current_thread.endSyscallCanceled(),
2280 else => |e| {2551 else => |e| {
2281 current_thread.endSyscall();2552 current_thread.endSyscall();
2282 switch (e) {2553 switch (e) {
...@@ -2284,7 +2555,7 @@ fn dirCreateFilePosix(...@@ -2284,7 +2555,7 @@ fn dirCreateFilePosix(
2284 .INVAL => |err| return errnoBug(err), // invalid parameters2555 .INVAL => |err| return errnoBug(err), // invalid parameters
2285 .NOLCK => return error.SystemResources,2556 .NOLCK => return error.SystemResources,
2286 .AGAIN => return error.WouldBlock,2557 .AGAIN => return error.WouldBlock,
2287 .OPNOTSUPP => return error.FileLocksNotSupported,2558 .OPNOTSUPP => return error.FileLocksUnsupported,
2288 else => |err| return posix.unexpectedErrno(err),2559 else => |err| return posix.unexpectedErrno(err),
2289 }2560 }
2290 },2561 },
...@@ -2338,10 +2609,10 @@ fn dirCreateFilePosix(...@@ -2338,10 +2609,10 @@ fn dirCreateFilePosix(
23382609
2339fn dirCreateFileWindows(2610fn dirCreateFileWindows(
2340 userdata: ?*anyopaque,2611 userdata: ?*anyopaque,
2341 dir: Io.Dir,2612 dir: Dir,
2342 sub_path: []const u8,2613 sub_path: []const u8,
2343 flags: Io.File.CreateFlags,2614 flags: File.CreateFlags,
2344) Io.File.OpenError!Io.File {2615) File.OpenError!File {
2345 const w = windows;2616 const w = windows;
2346 const t: *Threaded = @ptrCast(@alignCast(userdata));2617 const t: *Threaded = @ptrCast(@alignCast(userdata));
2347 const current_thread = Thread.getCurrent(t);2618 const current_thread = Thread.getCurrent(t);
...@@ -2367,35 +2638,42 @@ fn dirCreateFileWindows(...@@ -2367,35 +2638,42 @@ fn dirCreateFileWindows(
2367 .OPEN_IF,2638 .OPEN_IF,
2368 });2639 });
2369 errdefer w.CloseHandle(handle);2640 errdefer w.CloseHandle(handle);
2641
2370 var io_status_block: w.IO_STATUS_BLOCK = undefined;2642 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;
2373 const exclusive = switch (flags.lock) {2643 const exclusive = switch (flags.lock) {
2374 .none => return .{ .handle = handle },2644 .none => return .{ .handle = handle },
2375 .shared => false,2645 .shared => false,
2376 .exclusive => true,2646 .exclusive => true,
2377 };2647 };
2378 try w.LockFile(2648 const status = w.ntdll.NtLockFile(
2379 handle,2649 handle,
2380 null,2650 null,
2381 null,2651 null,
2382 null,2652 null,
2383 &io_status_block,2653 &io_status_block,
2384 &range_off,2654 &windows_lock_range_off,
2385 &range_len,2655 &windows_lock_range_len,
2386 null,2656 null,
2387 @intFromBool(flags.lock_nonblocking),2657 @intFromBool(flags.lock_nonblocking),
2388 @intFromBool(exclusive),2658 @intFromBool(exclusive),
2389 );2659 );
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
2390 return .{ .handle = handle };2668 return .{ .handle = handle };
2391}2669}
23922670
2393fn dirCreateFileWasi(2671fn dirCreateFileWasi(
2394 userdata: ?*anyopaque,2672 userdata: ?*anyopaque,
2395 dir: Io.Dir,2673 dir: Dir,
2396 sub_path: []const u8,2674 sub_path: []const u8,
2397 flags: Io.File.CreateFlags,2675 flags: File.CreateFlags,
2398) Io.File.OpenError!Io.File {2676) File.OpenError!File {
2399 const t: *Threaded = @ptrCast(@alignCast(userdata));2677 const t: *Threaded = @ptrCast(@alignCast(userdata));
2400 const current_thread = Thread.getCurrent(t);2678 const current_thread = Thread.getCurrent(t);
2401 const wasi = std.os.wasi;2679 const wasi = std.os.wasi;
...@@ -2436,7 +2714,6 @@ fn dirCreateFileWasi(...@@ -2436,7 +2714,6 @@ fn dirCreateFileWasi(
2436 try current_thread.checkCancel();2714 try current_thread.checkCancel();
2437 continue;2715 continue;
2438 },2716 },
2439 .CANCELED => return current_thread.endSyscallCanceled(),
2440 else => |e| {2717 else => |e| {
2441 current_thread.endSyscall();2718 current_thread.endSyscall();
2442 switch (e) {2719 switch (e) {
...@@ -2476,10 +2753,10 @@ const dirOpenFile = switch (native_os) {...@@ -2476,10 +2753,10 @@ const dirOpenFile = switch (native_os) {
24762753
2477fn dirOpenFilePosix(2754fn dirOpenFilePosix(
2478 userdata: ?*anyopaque,2755 userdata: ?*anyopaque,
2479 dir: Io.Dir,2756 dir: Dir,
2480 sub_path: []const u8,2757 sub_path: []const u8,
2481 flags: Io.File.OpenFlags,2758 flags: File.OpenFlags,
2482) Io.File.OpenError!Io.File {2759) File.OpenError!File {
2483 const t: *Threaded = @ptrCast(@alignCast(userdata));2760 const t: *Threaded = @ptrCast(@alignCast(userdata));
2484 const current_thread = Thread.getCurrent(t);2761 const current_thread = Thread.getCurrent(t);
24852762
...@@ -2490,6 +2767,7 @@ fn dirOpenFilePosix(...@@ -2490,6 +2767,7 @@ fn dirOpenFilePosix(
2490 .wasi => .{2767 .wasi => .{
2491 .read = flags.mode != .write_only,2768 .read = flags.mode != .write_only,
2492 .write = flags.mode != .read_only,2769 .write = flags.mode != .read_only,
2770 .NOFOLLOW = !flags.follow_symlinks,
2493 },2771 },
2494 else => .{2772 else => .{
2495 .ACCMODE = switch (flags.mode) {2773 .ACCMODE = switch (flags.mode) {
...@@ -2497,11 +2775,13 @@ fn dirOpenFilePosix(...@@ -2497,11 +2775,13 @@ fn dirOpenFilePosix(
2497 .write_only => .WRONLY,2775 .write_only => .WRONLY,
2498 .read_write => .RDWR,2776 .read_write => .RDWR,
2499 },2777 },
2778 .NOFOLLOW = !flags.follow_symlinks,
2500 },2779 },
2501 };2780 };
2502 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;2781 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
2503 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;2782 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
2504 if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty;2783 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
2506 // Use the O locking flags if the os supports them to acquire the lock2786 // Use the O locking flags if the os supports them to acquire the lock
2507 // atomically. Note that the NONBLOCK flag is removed after the openat()2787 // atomically. Note that the NONBLOCK flag is removed after the openat()
...@@ -2530,7 +2810,6 @@ fn dirOpenFilePosix(...@@ -2530,7 +2810,6 @@ fn dirOpenFilePosix(
2530 try current_thread.checkCancel();2810 try current_thread.checkCancel();
2531 continue;2811 continue;
2532 },2812 },
2533 .CANCELED => return current_thread.endSyscallCanceled(),
2534 else => |e| {2813 else => |e| {
2535 current_thread.endSyscall();2814 current_thread.endSyscall();
2536 switch (e) {2815 switch (e) {
...@@ -2547,14 +2826,14 @@ fn dirOpenFilePosix(...@@ -2547,14 +2826,14 @@ fn dirOpenFilePosix(
2547 .NFILE => return error.SystemFdQuotaExceeded,2826 .NFILE => return error.SystemFdQuotaExceeded,
2548 .NODEV => return error.NoDevice,2827 .NODEV => return error.NoDevice,
2549 .NOENT => return error.FileNotFound,2828 .NOENT => return error.FileNotFound,
2550 .SRCH => return error.ProcessNotFound,2829 .SRCH => return error.FileNotFound, // Linux when opening procfs files.
2551 .NOMEM => return error.SystemResources,2830 .NOMEM => return error.SystemResources,
2552 .NOSPC => return error.NoSpaceLeft,2831 .NOSPC => return error.NoSpaceLeft,
2553 .NOTDIR => return error.NotDir,2832 .NOTDIR => return error.NotDir,
2554 .PERM => return error.PermissionDenied,2833 .PERM => return error.PermissionDenied,
2555 .EXIST => return error.PathAlreadyExists,2834 .EXIST => return error.PathAlreadyExists,
2556 .BUSY => return error.DeviceBusy,2835 .BUSY => return error.DeviceBusy,
2557 .OPNOTSUPP => return error.FileLocksNotSupported,2836 .OPNOTSUPP => return error.FileLocksUnsupported,
2558 .AGAIN => return error.WouldBlock,2837 .AGAIN => return error.WouldBlock,
2559 .TXTBSY => return error.FileBusy,2838 .TXTBSY => return error.FileBusy,
2560 .NXIO => return error.NoDevice,2839 .NXIO => return error.NoDevice,
...@@ -2566,6 +2845,18 @@ fn dirOpenFilePosix(...@@ -2566,6 +2845,18 @@ fn dirOpenFilePosix(
2566 };2845 };
2567 errdefer posix.close(fd);2846 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
2569 if (have_flock and !have_flock_open_flags and flags.lock != .none) {2860 if (have_flock and !have_flock_open_flags and flags.lock != .none) {
2570 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;2861 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
2571 const lock_flags = switch (flags.lock) {2862 const lock_flags = switch (flags.lock) {
...@@ -2584,7 +2875,6 @@ fn dirOpenFilePosix(...@@ -2584,7 +2875,6 @@ fn dirOpenFilePosix(
2584 try current_thread.checkCancel();2875 try current_thread.checkCancel();
2585 continue;2876 continue;
2586 },2877 },
2587 .CANCELED => return current_thread.endSyscallCanceled(),
2588 else => |e| {2878 else => |e| {
2589 current_thread.endSyscall();2879 current_thread.endSyscall();
2590 switch (e) {2880 switch (e) {
...@@ -2592,7 +2882,7 @@ fn dirOpenFilePosix(...@@ -2592,7 +2882,7 @@ fn dirOpenFilePosix(
2592 .INVAL => |err| return errnoBug(err), // invalid parameters2882 .INVAL => |err| return errnoBug(err), // invalid parameters
2593 .NOLCK => return error.SystemResources,2883 .NOLCK => return error.SystemResources,
2594 .AGAIN => return error.WouldBlock,2884 .AGAIN => return error.WouldBlock,
2595 .OPNOTSUPP => return error.FileLocksNotSupported,2885 .OPNOTSUPP => return error.FileLocksUnsupported,
2596 else => |err| return posix.unexpectedErrno(err),2886 else => |err| return posix.unexpectedErrno(err),
2597 }2887 }
2598 },2888 },
...@@ -2613,7 +2903,6 @@ fn dirOpenFilePosix(...@@ -2613,7 +2903,6 @@ fn dirOpenFilePosix(
2613 try current_thread.checkCancel();2903 try current_thread.checkCancel();
2614 continue;2904 continue;
2615 },2905 },
2616 .CANCELED => return current_thread.endSyscallCanceled(),
2617 else => |err| {2906 else => |err| {
2618 current_thread.endSyscall();2907 current_thread.endSyscall();
2619 return posix.unexpectedErrno(err);2908 return posix.unexpectedErrno(err);
...@@ -2634,7 +2923,6 @@ fn dirOpenFilePosix(...@@ -2634,7 +2923,6 @@ fn dirOpenFilePosix(
2634 try current_thread.checkCancel();2923 try current_thread.checkCancel();
2635 continue;2924 continue;
2636 },2925 },
2637 .CANCELED => return current_thread.endSyscallCanceled(),
2638 else => |err| {2926 else => |err| {
2639 current_thread.endSyscall();2927 current_thread.endSyscall();
2640 return posix.unexpectedErrno(err);2928 return posix.unexpectedErrno(err);
...@@ -2648,10 +2936,10 @@ fn dirOpenFilePosix(...@@ -2648,10 +2936,10 @@ fn dirOpenFilePosix(
26482936
2649fn dirOpenFileWindows(2937fn dirOpenFileWindows(
2650 userdata: ?*anyopaque,2938 userdata: ?*anyopaque,
2651 dir: Io.Dir,2939 dir: Dir,
2652 sub_path: []const u8,2940 sub_path: []const u8,
2653 flags: Io.File.OpenFlags,2941 flags: File.OpenFlags,
2654) Io.File.OpenError!Io.File {2942) File.OpenError!File {
2655 const t: *Threaded = @ptrCast(@alignCast(userdata));2943 const t: *Threaded = @ptrCast(@alignCast(userdata));
2656 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);2944 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
2657 const sub_path_w = sub_path_w_array.span();2945 const sub_path_w = sub_path_w_array.span();
...@@ -2663,10 +2951,11 @@ pub fn dirOpenFileWtf16(...@@ -2663,10 +2951,11 @@ pub fn dirOpenFileWtf16(
2663 t: *Threaded,2951 t: *Threaded,
2664 dir_handle: ?windows.HANDLE,2952 dir_handle: ?windows.HANDLE,
2665 sub_path_w: [:0]const u16,2953 sub_path_w: [:0]const u16,
2666 flags: Io.File.OpenFlags,2954 flags: File.OpenFlags,
2667) Io.File.OpenError!Io.File {2955) File.OpenError!File {
2668 if (std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;2956 const allow_directory = flags.allow_directory and !flags.isWrite();
2669 if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir;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;
2670 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;2959 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
2671 const current_thread = Thread.getCurrent(t);2960 const current_thread = Thread.getCurrent(t);
2672 const w = windows;2961 const w = windows;
...@@ -2711,7 +3000,7 @@ pub fn dirOpenFileWtf16(...@@ -2711,7 +3000,7 @@ pub fn dirOpenFileWtf16(
2711 .OPEN,3000 .OPEN,
2712 .{3001 .{
2713 .IO = if (flags.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS,3002 .IO = if (flags.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS,
2714 .NON_DIRECTORY_FILE = true,3003 .NON_DIRECTORY_FILE = !allow_directory,
2715 .OPEN_REPARSE_POINT = !flags.follow_symlinks,3004 .OPEN_REPARSE_POINT = !flags.follow_symlinks,
2716 },3005 },
2717 null,3006 null,
...@@ -2763,34 +3052,39 @@ pub fn dirOpenFileWtf16(...@@ -2763,34 +3052,39 @@ pub fn dirOpenFileWtf16(
2763 };3052 };
2764 errdefer w.CloseHandle(handle);3053 errdefer w.CloseHandle(handle);
27653054
2766 const range_off: w.LARGE_INTEGER = 0;
2767 const range_len: w.LARGE_INTEGER = 1;
2768 const exclusive = switch (flags.lock) {3055 const exclusive = switch (flags.lock) {
2769 .none => return .{ .handle = handle },3056 .none => return .{ .handle = handle },
2770 .shared => false,3057 .shared => false,
2771 .exclusive => true,3058 .exclusive => true,
2772 };3059 };
2773 try w.LockFile(3060 const status = w.ntdll.NtLockFile(
2774 handle,3061 handle,
2775 null,3062 null,
2776 null,3063 null,
2777 null,3064 null,
2778 &io_status_block,3065 &io_status_block,
2779 &range_off,3066 &windows_lock_range_off,
2780 &range_len,3067 &windows_lock_range_len,
2781 null,3068 null,
2782 @intFromBool(flags.lock_nonblocking),3069 @intFromBool(flags.lock_nonblocking),
2783 @intFromBool(exclusive),3070 @intFromBool(exclusive),
2784 );3071 );
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 }
2785 return .{ .handle = handle };3079 return .{ .handle = handle };
2786}3080}
27873081
2788fn dirOpenFileWasi(3082fn dirOpenFileWasi(
2789 userdata: ?*anyopaque,3083 userdata: ?*anyopaque,
2790 dir: Io.Dir,3084 dir: Dir,
2791 sub_path: []const u8,3085 sub_path: []const u8,
2792 flags: Io.File.OpenFlags,3086 flags: File.OpenFlags,
2793) Io.File.OpenError!Io.File {3087) File.OpenError!File {
2794 if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags);3088 if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags);
2795 const t: *Threaded = @ptrCast(@alignCast(userdata));3089 const t: *Threaded = @ptrCast(@alignCast(userdata));
2796 const current_thread = Thread.getCurrent(t);3090 const current_thread = Thread.getCurrent(t);
...@@ -2827,15 +3121,13 @@ fn dirOpenFileWasi(...@@ -2827,15 +3121,13 @@ fn dirOpenFileWasi(
2827 while (true) {3121 while (true) {
2828 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {3122 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
2829 .SUCCESS => {3123 .SUCCESS => {
2830 errdefer posix.close(fd);
2831 current_thread.endSyscall();3124 current_thread.endSyscall();
2832 return .{ .handle = fd };3125 break;
2833 },3126 },
2834 .INTR => {3127 .INTR => {
2835 try current_thread.checkCancel();3128 try current_thread.checkCancel();
2836 continue;3129 continue;
2837 },3130 },
2838 .CANCELED => return current_thread.endSyscallCanceled(),
2839 else => |e| {3131 else => |e| {
2840 current_thread.endSyscall();3132 current_thread.endSyscall();
2841 switch (e) {3133 switch (e) {
...@@ -2863,6 +3155,21 @@ fn dirOpenFileWasi(...@@ -2863,6 +3155,21 @@ fn dirOpenFileWasi(
2863 },3155 },
2864 }3156 }
2865 }3157 }
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 };
2866}3173}
28673174
2868const dirOpenDir = switch (native_os) {3175const dirOpenDir = switch (native_os) {
...@@ -2874,10 +3181,10 @@ const dirOpenDir = switch (native_os) {...@@ -2874,10 +3181,10 @@ const dirOpenDir = switch (native_os) {
2874/// This function is also used for WASI when libc is linked.3181/// This function is also used for WASI when libc is linked.
2875fn dirOpenDirPosix(3182fn dirOpenDirPosix(
2876 userdata: ?*anyopaque,3183 userdata: ?*anyopaque,
2877 dir: Io.Dir,3184 dir: Dir,
2878 sub_path: []const u8,3185 sub_path: []const u8,
2879 options: Io.Dir.OpenOptions,3186 options: Dir.OpenOptions,
2880) Io.Dir.OpenError!Io.Dir {3187) Dir.OpenError!Dir {
2881 const t: *Threaded = @ptrCast(@alignCast(userdata));3188 const t: *Threaded = @ptrCast(@alignCast(userdata));
28823189
2883 if (is_windows) {3190 if (is_windows) {
...@@ -2919,7 +3226,6 @@ fn dirOpenDirPosix(...@@ -2919,7 +3226,6 @@ fn dirOpenDirPosix(
2919 try current_thread.checkCancel();3226 try current_thread.checkCancel();
2920 continue;3227 continue;
2921 },3228 },
2922 .CANCELED => return current_thread.endSyscallCanceled(),
2923 else => |e| {3229 else => |e| {
2924 current_thread.endSyscall();3230 current_thread.endSyscall();
2925 switch (e) {3231 switch (e) {
...@@ -2948,10 +3254,10 @@ fn dirOpenDirPosix(...@@ -2948,10 +3254,10 @@ fn dirOpenDirPosix(
29483254
2949fn dirOpenDirHaiku(3255fn dirOpenDirHaiku(
2950 userdata: ?*anyopaque,3256 userdata: ?*anyopaque,
2951 dir: Io.Dir,3257 dir: Dir,
2952 sub_path: []const u8,3258 sub_path: []const u8,
2953 options: Io.Dir.OpenOptions,3259 options: Dir.OpenOptions,
2954) Io.Dir.OpenError!Io.Dir {3260) Dir.OpenError!Dir {
2955 const t: *Threaded = @ptrCast(@alignCast(userdata));3261 const t: *Threaded = @ptrCast(@alignCast(userdata));
2956 const current_thread = Thread.getCurrent(t);3262 const current_thread = Thread.getCurrent(t);
29573263
...@@ -2972,7 +3278,6 @@ fn dirOpenDirHaiku(...@@ -2972,7 +3278,6 @@ fn dirOpenDirHaiku(
2972 try current_thread.checkCancel();3278 try current_thread.checkCancel();
2973 continue;3279 continue;
2974 },3280 },
2975 .CANCELED => return current_thread.endSyscallCanceled(),
2976 else => |e| {3281 else => |e| {
2977 current_thread.endSyscall();3282 current_thread.endSyscall();
2978 switch (e) {3283 switch (e) {
...@@ -2999,10 +3304,10 @@ fn dirOpenDirHaiku(...@@ -2999,10 +3304,10 @@ fn dirOpenDirHaiku(
29993304
3000pub fn dirOpenDirWindows(3305pub fn dirOpenDirWindows(
3001 t: *Io.Threaded,3306 t: *Io.Threaded,
3002 dir: Io.Dir,3307 dir: Dir,
3003 sub_path_w: [:0]const u16,3308 sub_path_w: [:0]const u16,
3004 options: Io.Dir.OpenOptions,3309 options: Dir.OpenOptions,
3005) Io.Dir.OpenError!Io.Dir {3310) Dir.OpenError!Dir {
3006 const current_thread = Thread.getCurrent(t);3311 const current_thread = Thread.getCurrent(t);
3007 const w = windows;3312 const w = windows;
30083313
...@@ -3013,7 +3318,7 @@ pub fn dirOpenDirWindows(...@@ -3013,7 +3318,7 @@ pub fn dirOpenDirWindows(
3013 .Buffer = @constCast(sub_path_w.ptr),3318 .Buffer = @constCast(sub_path_w.ptr),
3014 };3319 };
3015 var io_status_block: w.IO_STATUS_BLOCK = undefined;3320 var io_status_block: w.IO_STATUS_BLOCK = undefined;
3016 var result: Io.Dir = .{ .handle = undefined };3321 var result: Dir = .{ .handle = undefined };
3017 try current_thread.checkCancel();3322 try current_thread.checkCancel();
3018 const rc = w.ntdll.NtCreateFile(3323 const rc = w.ntdll.NtCreateFile(
3019 &result.handle,3324 &result.handle,
...@@ -3068,147 +3373,4298 @@ pub fn dirOpenDirWindows(...@@ -3068,147 +3373,4298 @@ pub fn dirOpenDirWindows(
3068 }3373 }
3069}3374}
30703375
3071const MakeOpenDirAccessMaskWOptions = struct {3376fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
3072 no_follow: bool,
3073 create_disposition: u32,
3074};
3075
3076fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void {
3077 const t: *Threaded = @ptrCast(@alignCast(userdata));3377 const t: *Threaded = @ptrCast(@alignCast(userdata));
3078 _ = t;3378 _ = t;
3079 posix.close(dir.handle);3379 for (dirs) |dir| posix.close(dir.handle);
3080}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(3393fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3083 userdata: ?*anyopaque,3394 const linux = std.os.linux;
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);
3089 const t: *Threaded = @ptrCast(@alignCast(userdata));3395 const t: *Threaded = @ptrCast(@alignCast(userdata));
3090 const current_thread = Thread.getCurrent(t);3396 const current_thread = Thread.getCurrent(t);
3091 const wasi = std.os.wasi;3397 var buffer_index: usize = 0;
30923398 while (buffer.len - buffer_index != 0) {
3093 var base: std.os.wasi.rights_t = .{3399 if (dr.end - dr.index == 0) {
3094 .FD_FILESTAT_GET = true,3400 // Refill the buffer, unless we've already created references to
3095 .FD_FDSTAT_SET_FLAGS = true,3401 // buffered data.
3096 .FD_FILESTAT_SET_TIMES = true,3402 if (buffer_index != 0) break;
3097 };3403 if (dr.state == .reset) {
3098 if (options.access_sub_paths) {3404 posixSeekTo(current_thread, dr.dir.handle, 0) catch |err| switch (err) {
3099 base.FD_READDIR = true;3405 error.Unseekable => return error.Unexpected,
3100 base.PATH_CREATE_DIRECTORY = true;3406 else => |e| return e,
3101 base.PATH_CREATE_FILE = true;3407 };
3102 base.PATH_LINK_SOURCE = true;3408 dr.state = .reading;
3103 base.PATH_LINK_TARGET = true;3409 }
3104 base.PATH_OPEN = true;3410 try current_thread.beginSyscall();
3105 base.PATH_READLINK = true;3411 const n = while (true) {
3106 base.PATH_RENAME_SOURCE = true;3412 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
3107 base.PATH_RENAME_TARGET = true;3413 switch (linux.errno(rc)) {
3108 base.PATH_FILESTAT_GET = true;3414 .SUCCESS => {
3109 base.PATH_FILESTAT_SET_SIZE = true;3415 current_thread.endSyscall();
3110 base.PATH_FILESTAT_SET_TIMES = true;3416 break rc;
3111 base.PATH_SYMLINK = true;3417 },
3112 base.PATH_REMOVE_DIRECTORY = true;3418 .INTR => {
3113 base.PATH_UNLINK_FILE = true;3419 try current_thread.checkCancel();
3114 }3420 continue;
31153421 },
3116 const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks };3422 else => |e| {
3117 const oflags: wasi.oflags_t = .{ .DIRECTORY = true };3423 current_thread.endSyscall();
3118 const fdflags: wasi.fdflags_t = .{};3424 switch (e) {
3119 var fd: posix.fd_t = undefined;3425 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
3120 try current_thread.beginSyscall();3426 .FAULT => |err| return errnoBug(err),
3121 while (true) {3427 .NOTDIR => |err| return errnoBug(err),
3122 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) {3428 // To be consistent across platforms, iteration
3123 .SUCCESS => {3429 // ends if the directory being iterated is deleted
3124 current_thread.endSyscall();3430 // during iteration. This matches the behavior of
3125 return .{ .handle = fd };3431 // non-Linux, non-WASI UNIX platforms.
3126 },3432 .NOENT => {
3127 .INTR => {3433 dr.state = .finished;
3128 try current_thread.checkCancel();3434 return 0;
3129 continue;3435 },
3130 },3436 // This can occur when reading /proc/$PID/net, or
3131 .CANCELED => return current_thread.endSyscallCanceled(),3437 // if the provided buffer is too small. Neither
3132 else => |e| {3438 // scenario is intended to be handled by this API.
3133 current_thread.endSyscall();3439 .INVAL => return error.Unexpected,
3134 switch (e) {3440 .ACCES => return error.AccessDenied, // Lacking permission to iterate this directory.
3135 .FAULT => |err| return errnoBug(err),3441 else => |err| return posix.unexpectedErrno(err),
3136 .INVAL => return error.BadPathName,3442 }
3137 .BADF => |err| return errnoBug(err), // File descriptor used after closed.3443 },
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),
3152 }3444 }
3153 },3445 };
3446 if (n == 0) {
3447 dr.state = .finished;
3448 return 0;
3449 }
3450 dr.index = 0;
3451 dr.end = n;
3154 }3452 }
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;
3155 }3487 }
3488 return buffer_index;
3156}3489}
31573490
3158fn fileClose(userdata: ?*anyopaque, file: Io.File) void {3491fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3159 const t: *Threaded = @ptrCast(@alignCast(userdata));3492 const t: *Threaded = @ptrCast(@alignCast(userdata));
3160 _ = t;3493 const current_thread = Thread.getCurrent(t);
3161 posix.close(file.handle);3494 const Header = extern struct {
3162}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) {7468 if (iovlen == 0) return 0;
3165 .windows => fileReadStreamingWindows,7469
3166 else => fileReadStreamingPosix,7470 if (native_os == .wasi and !builtin.link_libc) {
3167};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 {
3170 const t: *Threaded = @ptrCast(@alignCast(userdata));7592 const t: *Threaded = @ptrCast(@alignCast(userdata));
3171 const current_thread = Thread.getCurrent(t);7593 const current_thread = Thread.getCurrent(t);
31727594
3173 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;7595 if (is_windows) {
3174 var i: usize = 0;7596 if (header.len != 0) {
3175 for (data) |buf| {7597 return writeFileStreamingWindows(current_thread, file.handle, header);
3176 if (iovecs_buffer.len - i == 0) break;7598 }
3177 if (buf.len != 0) {7599 for (data[0 .. data.len - 1]) |buf| {
3178 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };7600 if (buf.len == 0) continue;
3179 i += 1;7601 return writeFileStreamingWindows(current_thread, file.handle, buf);
3180 }7602 }
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);
3181 }7606 }
3182 const dest = iovecs_buffer[0..i];7607
3183 assert(dest[0].len > 0);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
3185 if (native_os == .wasi and !builtin.link_libc) {7641 if (native_os == .wasi and !builtin.link_libc) {
7642 var n_written: usize = undefined;
3186 try current_thread.beginSyscall();7643 try current_thread.beginSyscall();
3187 while (true) {7644 while (true) {
3188 var nread: usize = undefined;7645 switch (std.os.wasi.fd_write(file.handle, &iovecs, iovlen, &n_written)) {
3189 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
3190 .SUCCESS => {7646 .SUCCESS => {
3191 current_thread.endSyscall();7647 current_thread.endSyscall();
3192 return nread;7648 return n_written;
3193 },7649 },
3194 .INTR => {7650 .INTR => {
3195 try current_thread.checkCancel();7651 try current_thread.checkCancel();
3196 continue;7652 continue;
3197 },7653 },
3198 .CANCELED => return current_thread.endSyscallCanceled(),
3199 else => |e| {7654 else => |e| {
3200 current_thread.endSyscall();7655 current_thread.endSyscall();
3201 switch (e) {7656 switch (e) {
3202 .INVAL => |err| return errnoBug(err),7657 .INVAL => |err| return errnoBug(err),
3203 .FAULT => |err| return errnoBug(err),7658 .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,
3205 .IO => return error.InputOutput,7664 .IO => return error.InputOutput,
3206 .ISDIR => return error.IsDir,7665 .NOSPC => return error.NoSpaceLeft,
3207 .NOBUFS => return error.SystemResources,7666 .PERM => return error.PermissionDenied,
3208 .NOMEM => return error.SystemResources,7667 .PIPE => return error.BrokenPipe,
3209 .NOTCONN => return error.SocketUnconnected,
3210 .CONNRESET => return error.ConnectionResetByPeer,
3211 .TIMEDOUT => return error.Timeout,
3212 .NOTCAPABLE => return error.AccessDenied,7668 .NOTCAPABLE => return error.AccessDenied,
3213 else => |err| return posix.unexpectedErrno(err),7669 else => |err| return posix.unexpectedErrno(err),
3214 }7670 }
...@@ -3219,7 +7675,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io...@@ -3219,7 +7675,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io
32197675
3220 try current_thread.beginSyscall();7676 try current_thread.beginSyscall();
3221 while (true) {7677 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));
3223 switch (posix.errno(rc)) {7679 switch (posix.errno(rc)) {
3224 .SUCCESS => {7680 .SUCCESS => {
3225 current_thread.endSyscall();7681 current_thread.endSyscall();
...@@ -3229,25 +7685,22 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io...@@ -3229,25 +7685,22 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io
3229 try current_thread.checkCancel();7685 try current_thread.checkCancel();
3230 continue;7686 continue;
3231 },7687 },
3232 .CANCELED => return current_thread.endSyscallCanceled(),
3233 else => |e| {7688 else => |e| {
3234 current_thread.endSyscall();7689 current_thread.endSyscall();
3235 switch (e) {7690 switch (e) {
3236 .INVAL => |err| return errnoBug(err),7691 .INVAL => |err| return errnoBug(err),
3237 .FAULT => |err| return errnoBug(err),7692 .FAULT => |err| return errnoBug(err),
3238 .SRCH => return error.ProcessNotFound,
3239 .AGAIN => return error.WouldBlock,7693 .AGAIN => return error.WouldBlock,
3240 .BADF => |err| {7694 .BADF => return error.NotOpenForWriting, // Can be a race condition.
3241 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.7695 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
3242 return errnoBug(err); // File descriptor used after closed.7696 .DQUOT => return error.DiskQuota,
3243 },7697 .FBIG => return error.FileTooBig,
3244 .IO => return error.InputOutput,7698 .IO => return error.InputOutput,
3245 .ISDIR => return error.IsDir,7699 .NOSPC => return error.NoSpaceLeft,
3246 .NOBUFS => return error.SystemResources,7700 .PERM => return error.PermissionDenied,
3247 .NOMEM => return error.SystemResources,7701 .PIPE => return error.BrokenPipe,
3248 .NOTCONN => return error.SocketUnconnected,7702 .CONNRESET => |err| return errnoBug(err), // Not a socket handle.
3249 .CONNRESET => return error.ConnectionResetByPeer,7703 .BUSY => return error.DeviceBusy,
3250 .TIMEDOUT => return error.Timeout,
3251 else => |err| return posix.unexpectedErrno(err),7704 else => |err| return posix.unexpectedErrno(err),
3252 }7705 }
3253 },7706 },
...@@ -3255,325 +7708,653 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io...@@ -3255,325 +7708,653 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io
3255 }7708 }
3256}7709}
32577710
3258fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize {7711fn writeFileStreamingWindows(
3259 const t: *Threaded = @ptrCast(@alignCast(userdata));7712 current_thread: *Thread,
3260 const current_thread = Thread.getCurrent(t);7713 handle: windows.HANDLE,
32617714 bytes: []const u8,
3262 const DWORD = windows.DWORD;7715) File.Writer.Error!usize {
3263 var index: usize = 0;7716 try current_thread.checkCancel();
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);
32677717
3268 while (true) {7718 var bytes_written: windows.DWORD = undefined;
3269 try current_thread.checkCancel();7719 const adjusted_len = std.math.lossyCast(u32, bytes.len);
3270 var n: DWORD = undefined;7720 if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) == 0) {
3271 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0)
3272 return n;
3273 switch (windows.GetLastError()) {7721 switch (windows.GetLastError()) {
3274 .IO_PENDING => |err| return windows.errorBug(err),7722 .INVALID_USER_BUFFER => return error.SystemResources,
3275 .OPERATION_ABORTED => continue,7723 .NOT_ENOUGH_MEMORY => return error.SystemResources,
3276 .BROKEN_PIPE => return 0,7724 .OPERATION_ABORTED => return error.Canceled,
3277 .HANDLE_EOF => return 0,7725 .NOT_ENOUGH_QUOTA => return error.SystemResources,
3278 .NETNAME_DELETED => return error.ConnectionResetByPeer,7726 .NO_DATA => return error.BrokenPipe,
7727 .INVALID_HANDLE => return error.NotOpenForWriting,
3279 .LOCK_VIOLATION => return error.LockViolation,7728 .LOCK_VIOLATION => return error.LockViolation,
3280 .ACCESS_DENIED => return error.AccessDenied,7729 .ACCESS_DENIED => return error.AccessDenied,
3281 .INVALID_HANDLE => return error.NotOpenForReading,7730 .WORKING_SET_QUOTA => return error.SystemResources,
3282 else => |err| return windows.unexpectedError(err),7731 else => |err| return windows.unexpectedError(err),
3283 }7732 }
3284 }7733 }
7734 return bytes_written;
3285}7735}
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 {
3288 const t: *Threaded = @ptrCast(@alignCast(userdata));7744 const t: *Threaded = @ptrCast(@alignCast(userdata));
3289 const current_thread = Thread.getCurrent(t);7745 const reader_buffered = file_reader.interface.buffered();
32907746 if (reader_buffered.len >= @intFromEnum(limit)) {
3291 if (!have_preadv) @compileError("TODO");7747 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
32927748 file_reader.interface.toss(n -| header.len);
3293 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;7749 return n;
3294 var i: usize = 0;7750 }
3295 for (data) |buf| {7751 const file_limit = @intFromEnum(limit) - reader_buffered.len;
3296 if (iovecs_buffer.len - i == 0) break;7752 const out_fd = file.handle;
3297 if (buf.len != 0) {7753 const in_fd = file_reader.file.handle;
3298 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };7754
3299 i += 1;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 }
3300 }7764 }
3301 }7765 }
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);
3306 try current_thread.beginSyscall();7962 try current_thread.beginSyscall();
3307 while (true) {7963 const n: usize = while (true) {
3308 var nread: usize = undefined;7964 const rc = sendfile_sym(out_fd, in_fd, off_ptr, count);
3309 switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) {7965 switch (posix.errno(rc)) {
3310 .SUCCESS => {7966 .SUCCESS => {
3311 current_thread.endSyscall();7967 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;
3313 },7976 },
3314 .INTR => {7977 .INTR => {
3315 try current_thread.checkCancel();7978 try current_thread.checkCancel();
3316 continue;7979 continue;
3317 },7980 },
3318 .CANCELED => return current_thread.endSyscallCanceled(),
3319 else => |e| {7981 else => |e| {
3320 current_thread.endSyscall();7982 current_thread.endSyscall();
3321 switch (e) {7983 assert(error.Unexpected == switch (e) {
3322 .INVAL => |err| return errnoBug(err),7984 .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket
3323 .FAULT => |err| return errnoBug(err),7985 .AGAIN => return error.WouldBlock,
3324 .AGAIN => |err| return errnoBug(err),
3325 .BADF => return error.NotOpenForReading, // File operation on directory.
3326 .IO => return error.InputOutput,7986 .IO => return error.InputOutput,
3327 .ISDIR => return error.IsDir,7987 .PIPE => return error.BrokenPipe,
3328 .NOBUFS => return error.SystemResources,
3329 .NOMEM => return error.SystemResources,7988 .NOMEM => return error.SystemResources,
3330 .NOTCONN => return error.SocketUnconnected,7989 .NXIO, .SPIPE => {
3331 .CONNRESET => return error.ConnectionResetByPeer,7990 file_reader.mode = file_reader.mode.toStreaming();
3332 .TIMEDOUT => return error.Timeout,7991 const pos = file_reader.pos;
3333 .NXIO => return error.Unseekable,7992 if (pos != 0) {
3334 .SPIPE => return error.Unseekable,7993 file_reader.pos = 0;
3335 .OVERFLOW => return error.Unseekable,7994 file_reader.seekBy(@intCast(pos)) catch {
3336 .NOTCAPABLE => return error.AccessDenied,7995 file_reader.mode = .failure;
3337 else => |err| return posix.unexpectedErrno(err),7996 return error.ReadFailed;
3338 }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;
3339 },8010 },
3340 }8011 }
8012 };
8013 if (n == 0) {
8014 file_reader.size = file_reader.pos;
8015 return error.EndOfStream;
3341 }8016 }
8017 file_reader.pos += n;
8018 return n;
3342 }8019 }
33438020
3344 try current_thread.beginSyscall();8021 if (have_copy_file_range) cfr: {
3345 while (true) {8022 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr;
3346 const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset));8023 if (header.len != 0 or reader_buffered.len != 0) {
3347 switch (posix.errno(rc)) {8024 const n = try fileWriteStreaming(t, file, header, &.{limit.slice(reader_buffered)}, 1);
3348 .SUCCESS => {8025 file_reader.interface.toss(n -| header.len);
3349 current_thread.endSyscall();8026 return n;
3350 return @bitCast(rc);8027 }
3351 },8028 var off_in: i64 = undefined;
3352 .INTR => {8029 const off_in_ptr: ?*i64 = switch (file_reader.mode) {
3353 try current_thread.checkCancel();8030 .positional_simple, .streaming_simple => return error.Unimplemented,
3354 continue;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 }
3355 },8083 },
3356 .CANCELED => return current_thread.endSyscallCanceled(),8084 .freebsd => n: {
3357 else => |e| {8085 try current_thread.beginSyscall();
3358 current_thread.endSyscall();8086 while (true) {
3359 switch (e) {8087 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0);
3360 .INVAL => |err| return errnoBug(err),8088 switch (std.c.errno(rc)) {
3361 .FAULT => |err| return errnoBug(err),8089 .SUCCESS => {
3362 .SRCH => return error.ProcessNotFound,8090 current_thread.endSyscall();
3363 .AGAIN => return error.WouldBlock,8091 break :n @intCast(rc);
3364 .BADF => |err| {8092 },
3365 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.8093 .INTR => {
3366 return errnoBug(err); // File descriptor used after closed.8094 try current_thread.checkCancel();
3367 },8095 continue;
3368 .IO => return error.InputOutput,8096 },
3369 .ISDIR => return error.IsDir,8097 .OPNOTSUPP, .INVAL, .NOSYS => {
3370 .NOBUFS => return error.SystemResources,8098 // Give calling code chance to observe before trying
3371 .NOMEM => return error.SystemResources,8099 // something else.
3372 .NOTCONN => return error.SocketUnconnected,8100 current_thread.endSyscall();
3373 .CONNRESET => return error.ConnectionResetByPeer,8101 @atomicStore(UseCopyFileRange, &t.use_copy_file_range, .disabled, .monotonic);
3374 .TIMEDOUT => return error.Timeout,8102 return 0;
3375 .NXIO => return error.Unseekable,8103 },
3376 .SPIPE => return error.Unseekable,8104 else => |e| {
3377 .OVERFLOW => return error.Unseekable,8105 current_thread.endSyscall();
3378 else => |err| return posix.unexpectedErrno(err),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 }
3379 }8119 }
3380 },8120 },
8121 else => comptime unreachable,
8122 };
8123 if (n == 0) {
8124 file_reader.size = file_reader.pos;
8125 return error.EndOfStream;
3381 }8126 }
8127 file_reader.pos += n;
8128 return n;
3382 }8129 }
3383}
33848130
3385const fileReadPositional = switch (native_os) {8131 return error.Unimplemented;
3386 .windows => fileReadPositionalWindows,8132}
3387 else => fileReadPositionalPosix,
3388};
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 {
3391 const t: *Threaded = @ptrCast(@alignCast(userdata));8141 const t: *Threaded = @ptrCast(@alignCast(userdata));
3392 const current_thread = Thread.getCurrent(t);8142 _ = t;
33938143 _ = socket_handle;
3394 const DWORD = windows.DWORD;8144 _ = header;
33958145 _ = file_reader;
3396 var index: usize = 0;8146 _ = limit;
3397 while (data[index].len == 0) index += 1;8147 @panic("TODO implement netWriteFile");
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 }
3430}8148}
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 {
3433 const t: *Threaded = @ptrCast(@alignCast(userdata));8157 const t: *Threaded = @ptrCast(@alignCast(userdata));
3434 _ = t;8158 _ = t;
3435 _ = file;8159 _ = socket_handle;
3436 _ = offset;8160 _ = header;
3437 @panic("TODO implement fileSeekBy");8161 _ = file_reader;
8162 _ = limit;
8163 return error.NetworkDown;
3438}8164}
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 {
3441 const t: *Threaded = @ptrCast(@alignCast(userdata));8174 const t: *Threaded = @ptrCast(@alignCast(userdata));
3442 const current_thread = Thread.getCurrent(t);8175 const reader_buffered = file_reader.interface.buffered();
3443 const fd = file.handle;8176 if (reader_buffered.len >= @intFromEnum(limit)) {
34448177 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
3445 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {8178 file_reader.interface.toss(n -| header.len);
3446 try current_thread.beginSyscall();8179 return n;
3447 while (true) {8180 }
3448 var result: u64 = undefined;8181 const out_fd = file.handle;
3449 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {8182 const in_fd = file_reader.file.handle;
3450 .SUCCESS => {8183
3451 current_thread.endSyscall();8184 if (file_reader.size) |size| {
3452 return;8185 if (size - file_reader.pos == 0) {
3453 },8186 if (reader_buffered.len != 0) {
3454 .INTR => {8187 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
3455 try current_thread.checkCancel();8188 file_reader.interface.toss(n -| header.len);
3456 continue;8189 return n;
3457 },8190 } else {
3458 .CANCELED => return current_thread.endSyscallCanceled(),8191 return error.EndOfStream;
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 },
3470 }8192 }
3471 }8193 }
3472 }8194 }
34738195
3474 if (native_os == .windows) {8196 if (have_copy_file_range) cfr: {
3475 try current_thread.checkCancel();8197 if (@atomicLoad(UseCopyFileRange, &t.use_copy_file_range, .monotonic) == .disabled) break :cfr;
3476 return windows.SetFilePointerEx_BEGIN(fd, offset);8198 if (header.len != 0 or reader_buffered.len != 0) {
3477 }8199 const n = try fileWritePositional(t, file, header, &.{limit.slice(reader_buffered)}, 1, offset);
34788200 file_reader.interface.toss(n -| header.len);
3479 if (native_os == .wasi and !builtin.link_libc) while (true) {8201 return n;
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 },
3504 }8202 }
3505 };8203 var off_in: i64 = undefined;
35068204 const off_in_ptr: ?*i64 = switch (file_reader.mode) {
3507 if (posix.SEEK == void) return error.Unseekable;8205 .positional_simple, .streaming_simple => return error.Unimplemented,
35088206 .positional => p: {
3509 try current_thread.beginSyscall();8207 off_in = @intCast(file_reader.pos);
3510 while (true) {8208 break :p &off_in;
3511 switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) {8209 },
3512 .SUCCESS => {8210 .streaming => null,
3513 current_thread.endSyscall();8211 .failure => return error.ReadFailed,
3514 return;8212 };
3515 },8213 var off_out: i64 = @intCast(offset);
3516 .INTR => {8214 const current_thread = Thread.getCurrent(t);
3517 try current_thread.checkCancel();8215 const n: usize = switch (native_os) {
3518 continue;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 }
3519 },8260 },
3520 .CANCELED => return current_thread.endSyscallCanceled(),8261 .freebsd => n: {
3521 else => |e| {8262 try current_thread.beginSyscall();
3522 current_thread.endSyscall();8263 while (true) {
3523 switch (e) {8264 const rc = std.c.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0);
3524 .BADF => |err| return errnoBug(err), // File descriptor used after closed.8265 switch (std.c.errno(rc)) {
3525 .INVAL => return error.Unseekable,8266 .SUCCESS => {
3526 .OVERFLOW => return error.Unseekable,8267 current_thread.endSyscall();
3527 .SPIPE => return error.Unseekable,8268 break :n @intCast(rc);
3528 .NXIO => return error.Unseekable,8269 },
3529 else => |err| return posix.unexpectedErrno(err),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 }
3530 }8299 }
3531 },8300 },
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 }
3532 }8352 }
8353 file_reader.pos = size;
8354 return size;
3533 }8355 }
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 {8357 return error.Unimplemented;
3570 const t: *Threaded = @ptrCast(@alignCast(userdata));
3571 _ = t;
3572 while (true) {
3573 _ = file;
3574 _ = buffer;
3575 @panic("TODO implement fileWriteStreaming");
3576 }
3577}8358}
35788359
3579fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {8360fn 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 {...@@ -3673,7 +8454,6 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
3673 try current_thread.checkCancel();8454 try current_thread.checkCancel();
3674 continue;8455 continue;
3675 },8456 },
3676 .CANCELED => return current_thread.endSyscallCanceled(),
3677 else => |e| {8457 else => |e| {
3678 current_thread.endSyscall();8458 current_thread.endSyscall();
3679 switch (e) {8459 switch (e) {
...@@ -3751,7 +8531,6 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {...@@ -3751,7 +8531,6 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
3751 try current_thread.checkCancel();8531 try current_thread.checkCancel();
3752 continue;8532 continue;
3753 },8533 },
3754 .CANCELED => return current_thread.endSyscallCanceled(),
3755 // This prong handles success as well as unexpected errors.8534 // This prong handles success as well as unexpected errors.
3756 else => return current_thread.endSyscall(),8535 else => return current_thread.endSyscall(),
3757 }8536 }
...@@ -3825,7 +8604,6 @@ fn netListenIpPosix(...@@ -3825,7 +8604,6 @@ fn netListenIpPosix(
3825 try current_thread.checkCancel();8604 try current_thread.checkCancel();
3826 continue;8605 continue;
3827 },8606 },
3828 .CANCELED => return current_thread.endSyscallCanceled(),
3829 else => |e| {8607 else => |e| {
3830 current_thread.endSyscall();8608 current_thread.endSyscall();
3831 switch (e) {8609 switch (e) {
...@@ -3989,7 +8767,6 @@ fn netListenUnixPosix(...@@ -3989,7 +8767,6 @@ fn netListenUnixPosix(
3989 try current_thread.checkCancel();8767 try current_thread.checkCancel();
3990 continue;8768 continue;
3991 },8769 },
3992 .CANCELED => return current_thread.endSyscallCanceled(),
3993 else => |e| {8770 else => |e| {
3994 current_thread.endSyscall();8771 current_thread.endSyscall();
3995 switch (e) {8772 switch (e) {
...@@ -4113,7 +8890,6 @@ fn posixBindUnix(...@@ -4113,7 +8890,6 @@ fn posixBindUnix(
4113 try current_thread.checkCancel();8890 try current_thread.checkCancel();
4114 continue;8891 continue;
4115 },8892 },
4116 .CANCELED => return current_thread.endSyscallCanceled(),
4117 else => |e| {8893 else => |e| {
4118 current_thread.endSyscall();8894 current_thread.endSyscall();
4119 switch (e) {8895 switch (e) {
...@@ -4158,7 +8934,6 @@ fn posixBind(...@@ -4158,7 +8934,6 @@ fn posixBind(
4158 try current_thread.checkCancel();8934 try current_thread.checkCancel();
4159 continue;8935 continue;
4160 },8936 },
4161 .CANCELED => return current_thread.endSyscallCanceled(),
4162 else => |e| {8937 else => |e| {
4163 current_thread.endSyscall();8938 current_thread.endSyscall();
4164 switch (e) {8939 switch (e) {
...@@ -4194,7 +8969,6 @@ fn posixConnect(...@@ -4194,7 +8969,6 @@ fn posixConnect(
4194 try current_thread.checkCancel();8969 try current_thread.checkCancel();
4195 continue;8970 continue;
4196 },8971 },
4197 .CANCELED => return current_thread.endSyscallCanceled(),
4198 else => |e| {8972 else => |e| {
4199 current_thread.endSyscall();8973 current_thread.endSyscall();
4200 switch (e) {8974 switch (e) {
...@@ -4241,7 +9015,6 @@ fn posixConnectUnix(...@@ -4241,7 +9015,6 @@ fn posixConnectUnix(
4241 try current_thread.checkCancel();9015 try current_thread.checkCancel();
4242 continue;9016 continue;
4243 },9017 },
4244 .CANCELED => return current_thread.endSyscallCanceled(),
4245 else => |e| {9018 else => |e| {
4246 current_thread.endSyscall();9019 current_thread.endSyscall();
4247 switch (e) {9020 switch (e) {
...@@ -4286,7 +9059,6 @@ fn posixGetSockName(...@@ -4286,7 +9059,6 @@ fn posixGetSockName(
4286 try current_thread.checkCancel();9059 try current_thread.checkCancel();
4287 continue;9060 continue;
4288 },9061 },
4289 .CANCELED => return current_thread.endSyscallCanceled(),
4290 else => |e| {9062 else => |e| {
4291 current_thread.endSyscall();9063 current_thread.endSyscall();
4292 switch (e) {9064 switch (e) {
...@@ -4354,7 +9126,6 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name...@@ -4354,7 +9126,6 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name
4354 try current_thread.checkCancel();9126 try current_thread.checkCancel();
4355 continue;9127 continue;
4356 },9128 },
4357 .CANCELED => return current_thread.endSyscallCanceled(),
4358 else => |e| {9129 else => |e| {
4359 current_thread.endSyscall();9130 current_thread.endSyscall();
4360 switch (e) {9131 switch (e) {
...@@ -4684,7 +9455,6 @@ fn openSocketPosix(...@@ -4684,7 +9455,6 @@ fn openSocketPosix(
4684 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {9455 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
4685 .SUCCESS => break,9456 .SUCCESS => break,
4686 .INTR => continue,9457 .INTR => continue,
4687 .CANCELED => return current_thread.endSyscallCanceled(),
4688 else => |err| {9458 else => |err| {
4689 current_thread.endSyscall();9459 current_thread.endSyscall();
4690 return posix.unexpectedErrno(err);9460 return posix.unexpectedErrno(err);
...@@ -4698,7 +9468,6 @@ fn openSocketPosix(...@@ -4698,7 +9468,6 @@ fn openSocketPosix(
4698 try current_thread.checkCancel();9468 try current_thread.checkCancel();
4699 continue;9469 continue;
4700 },9470 },
4701 .CANCELED => return current_thread.endSyscallCanceled(),
4702 else => |e| {9471 else => |e| {
4703 current_thread.endSyscall();9472 current_thread.endSyscall();
4704 switch (e) {9473 switch (e) {
...@@ -4800,7 +9569,6 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve...@@ -4800,7 +9569,6 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve
4800 try current_thread.checkCancel();9569 try current_thread.checkCancel();
4801 continue;9570 continue;
4802 },9571 },
4803 .CANCELED => return current_thread.endSyscallCanceled(),
4804 else => |e| {9572 else => |e| {
4805 current_thread.endSyscall();9573 current_thread.endSyscall();
4806 switch (e) {9574 switch (e) {
...@@ -4909,7 +9677,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net....@@ -4909,7 +9677,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
4909 try current_thread.checkCancel();9677 try current_thread.checkCancel();
4910 continue;9678 continue;
4911 },9679 },
4912 .CANCELED => return current_thread.endSyscallCanceled(),
4913 else => |e| {9680 else => |e| {
4914 current_thread.endSyscall();9681 current_thread.endSyscall();
4915 switch (e) {9682 switch (e) {
...@@ -4942,7 +9709,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net....@@ -4942,7 +9709,6 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
4942 try current_thread.checkCancel();9709 try current_thread.checkCancel();
4943 continue;9710 continue;
4944 },9711 },
4945 .CANCELED => return current_thread.endSyscallCanceled(),
4946 else => |e| {9712 else => |e| {
4947 current_thread.endSyscall();9713 current_thread.endSyscall();
4948 switch (e) {9714 switch (e) {
...@@ -5178,7 +9944,6 @@ fn netSendOne(...@@ -5178,7 +9944,6 @@ fn netSendOne(
5178 try current_thread.checkCancel();9944 try current_thread.checkCancel();
5179 continue;9945 continue;
5180 },9946 },
5181 .CANCELED => return current_thread.endSyscallCanceled(),
5182 else => |e| {9947 else => |e| {
5183 current_thread.endSyscall();9948 current_thread.endSyscall();
5184 switch (e) {9949 switch (e) {
...@@ -5255,7 +10020,6 @@ fn netSendMany(...@@ -5255,7 +10020,6 @@ fn netSendMany(
5255 try current_thread.checkCancel();10020 try current_thread.checkCancel();
5256 continue;10021 continue;
5257 },10022 },
5258 .CANCELED => return current_thread.endSyscallCanceled(),
5259 else => |e| {10023 else => |e| {
5260 current_thread.endSyscall();10024 current_thread.endSyscall();
5261 switch (e) {10025 switch (e) {
...@@ -5388,7 +10152,6 @@ fn netReceivePosix(...@@ -5388,7 +10152,6 @@ fn netReceivePosix(
5388 continue :recv;10152 continue :recv;
5389 },10153 },
5390 .INTR => continue,10154 .INTR => continue,
5391 .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i },
539210155
5393 .FAULT => |err| return .{ errnoBug(err), message_i },10156 .FAULT => |err| return .{ errnoBug(err), message_i },
5394 .INVAL => |err| return .{ errnoBug(err), message_i },10157 .INVAL => |err| return .{ errnoBug(err), message_i },
...@@ -5397,7 +10160,6 @@ fn netReceivePosix(...@@ -5397,7 +10160,6 @@ fn netReceivePosix(
5397 }10160 }
5398 },10161 },
5399 .INTR => continue,10162 .INTR => continue,
5400 .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i },
540110163
5402 .BADF => |err| return .{ errnoBug(err), message_i },10164 .BADF => |err| return .{ errnoBug(err), message_i },
5403 .NFILE => return .{ error.SystemFdQuotaExceeded, message_i },10165 .NFILE => return .{ error.SystemFdQuotaExceeded, message_i },
...@@ -5496,7 +10258,7 @@ fn netWritePosix(...@@ -5496,7 +10258,7 @@ fn netWritePosix(
5496 addBuf(&iovecs, &msg.iovlen, splat_buffer);10258 addBuf(&iovecs, &msg.iovlen, splat_buffer);
5497 remaining_splat -= splat_buffer.len;10259 remaining_splat -= splat_buffer.len;
5498 }10260 }
5499 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]);10261 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]);
5500 },10262 },
5501 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| {10263 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| {
5502 addBuf(&iovecs, &msg.iovlen, pattern);10264 addBuf(&iovecs, &msg.iovlen, pattern);
...@@ -5504,6 +10266,7 @@ fn netWritePosix(...@@ -5504,6 +10266,7 @@ fn netWritePosix(
5504 },10266 },
5505 };10267 };
5506 const flags = posix.MSG.NOSIGNAL;10268 const flags = posix.MSG.NOSIGNAL;
10269
5507 try current_thread.beginSyscall();10270 try current_thread.beginSyscall();
5508 while (true) {10271 while (true) {
5509 const rc = posix.system.sendmsg(fd, &msg, flags);10272 const rc = posix.system.sendmsg(fd, &msg, flags);
...@@ -5516,7 +10279,6 @@ fn netWritePosix(...@@ -5516,7 +10279,6 @@ fn netWritePosix(
5516 try current_thread.checkCancel();10279 try current_thread.checkCancel();
5517 continue;10280 continue;
5518 },10281 },
5519 .CANCELED => return current_thread.endSyscallCanceled(),
5520 else => |e| {10282 else => |e| {
5521 current_thread.endSyscall();10283 current_thread.endSyscall();
5522 switch (e) {10284 switch (e) {
...@@ -5580,7 +10342,7 @@ fn netWriteWindows(...@@ -5580,7 +10342,7 @@ fn netWriteWindows(
5580 addWsaBuf(&iovecs, &len, splat_buffer);10342 addWsaBuf(&iovecs, &len, splat_buffer);
5581 remaining_splat -= splat_buffer.len;10343 remaining_splat -= splat_buffer.len;
5582 }10344 }
5583 addWsaBuf(&iovecs, &len, splat_buffer[0..remaining_splat]);10345 addWsaBuf(&iovecs, &len, splat_buffer[0..@min(remaining_splat, splat_buffer.len)]);
5584 },10346 },
5585 else => for (0..@min(splat, iovecs.len - len)) |_| {10347 else => for (0..@min(splat, iovecs.len - len)) |_| {
5586 addWsaBuf(&iovecs, &len, pattern);10348 addWsaBuf(&iovecs, &len, pattern);
...@@ -5614,8 +10376,7 @@ fn netWriteWindows(...@@ -5614,8 +10376,7 @@ fn netWriteWindows(
5614 else => |err| err,10376 else => |err| err,
5615 };10377 };
5616 switch (wsa_error) {10378 switch (wsa_error) {
5617 .EINTR => continue,10379 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
5618 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(),
5619 .NOTINITIALISED => {10380 .NOTINITIALISED => {
5620 try initializeWsa(t);10381 try initializeWsa(t);
5621 continue;10382 continue;
...@@ -5667,7 +10428,14 @@ fn netWriteUnavailable(...@@ -5667,7 +10428,14 @@ fn netWriteUnavailable(
5667 return error.NetworkDown;10428 return error.NetworkDown;
5668}10429}
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 {
5671 // OS checks ptr addr before length so zero length vectors must be omitted.10439 // OS checks ptr addr before length so zero length vectors must be omitted.
5672 if (bytes.len == 0) return;10440 if (bytes.len == 0) return;
5673 if (v.len - i.* == 0) return;10441 if (v.len - i.* == 0) return;
...@@ -5675,18 +10443,18 @@ fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"),...@@ -5675,18 +10443,18 @@ fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"),
5675 i.* += 1;10443 i.* += 1;
5676}10444}
567710445
5678fn netClose(userdata: ?*anyopaque, handle: net.Socket.Handle) void {10446fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {
5679 const t: *Threaded = @ptrCast(@alignCast(userdata));10447 const t: *Threaded = @ptrCast(@alignCast(userdata));
5680 _ = t;10448 _ = t;
5681 switch (native_os) {10449 switch (native_os) {
5682 .windows => closeSocketWindows(handle),10450 .windows => for (handles) |handle| closeSocketWindows(handle),
5683 else => posix.close(handle),10451 else => for (handles) |handle| posix.close(handle),
5684 }10452 }
5685}10453}
568610454
5687fn netCloseUnavailable(userdata: ?*anyopaque, handle: net.Socket.Handle) void {10455fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {
5688 _ = userdata;10456 _ = userdata;
5689 _ = handle;10457 _ = handles;
5690 unreachable; // How you gonna close something that was impossible to open?10458 unreachable; // How you gonna close something that was impossible to open?
5691}10459}
569210460
...@@ -5727,7 +10495,6 @@ fn netInterfaceNameResolve(...@@ -5727,7 +10495,6 @@ fn netInterfaceNameResolve(
5727 try current_thread.checkCancel();10495 try current_thread.checkCancel();
5728 continue;10496 continue;
5729 },10497 },
5730 .CANCELED => return current_thread.endSyscallCanceled(),
5731 else => |e| {10498 else => |e| {
5732 current_thread.endSyscall();10499 current_thread.endSyscall();
5733 switch (e) {10500 switch (e) {
...@@ -5993,7 +10760,7 @@ fn netLookupFallible(...@@ -5993,7 +10760,7 @@ fn netLookupFallible(
5993 // TODO use dnsres_getaddrinfo10760 // TODO use dnsres_getaddrinfo
5994 }10761 }
599510762
5996 if (native_os.isDarwin()) {10763 if (is_darwin) {
5997 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution10764 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution
5998 }10765 }
599910766
...@@ -6031,7 +10798,6 @@ fn netLookupFallible(...@@ -6031,7 +10798,6 @@ fn netLookupFallible(
6031 try current_thread.checkCancel();10798 try current_thread.checkCancel();
6032 continue;10799 continue;
6033 },10800 },
6034 .CANCELED => return current_thread.endSyscallCanceled(),
6035 else => |e| {10801 else => |e| {
6036 current_thread.endSyscall();10802 current_thread.endSyscall();
6037 return posix.unexpectedErrno(e);10803 return posix.unexpectedErrno(e);
...@@ -6078,6 +10844,140 @@ fn netLookupFallible(...@@ -6078,6 +10844,140 @@ fn netLookupFallible(
6078 return error.OptionUnsupported;10844 return error.OptionUnsupported;
6079}10845}
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
6081pub const PosixAddress = extern union {10981pub const PosixAddress = extern union {
6082 any: posix.sockaddr,10982 any: posix.sockaddr,
6083 in: posix.sockaddr.in,10983 in: posix.sockaddr.in,
...@@ -6269,14 +11169,30 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t {...@@ -6269,14 +11169,30 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t {
6269 };11169 };
6270}11170}
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
6273 const atime = stx.atime;11188 const atime = stx.atime;
6274 const mtime = stx.mtime;11189 const mtime = stx.mtime;
6275 const ctime = stx.ctime;11190 const ctime = stx.ctime;
6276 return .{11191 return .{
6277 .inode = stx.ino,11192 .inode = stx.ino,
11193 .nlink = stx.nlink,
6278 .size = stx.size,11194 .size = stx.size,
6279 .mode = stx.mode,11195 .permissions = .fromMode(stx.mode),
6280 .kind = switch (stx.mode & std.os.linux.S.IFMT) {11196 .kind = switch (stx.mode & std.os.linux.S.IFMT) {
6281 std.os.linux.S.IFDIR => .directory,11197 std.os.linux.S.IFDIR => .directory,
6282 std.os.linux.S.IFCHR => .character_device,11198 std.os.linux.S.IFCHR => .character_device,
...@@ -6293,14 +11209,15 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat {...@@ -6293,14 +11209,15 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat {
6293 };11209 };
6294}11210}
629511211
6296fn statFromPosix(st: *const posix.Stat) Io.File.Stat {11212fn statFromPosix(st: *const posix.Stat) File.Stat {
6297 const atime = st.atime();11213 const atime = st.atime();
6298 const mtime = st.mtime();11214 const mtime = st.mtime();
6299 const ctime = st.ctime();11215 const ctime = st.ctime();
6300 return .{11216 return .{
6301 .inode = st.ino,11217 .inode = st.ino,
11218 .nlink = st.nlink,
6302 .size = @bitCast(st.size),11219 .size = @bitCast(st.size),
6303 .mode = st.mode,11220 .permissions = .fromMode(st.mode),
6304 .kind = k: {11221 .kind = k: {
6305 const m = st.mode & posix.S.IFMT;11222 const m = st.mode & posix.S.IFMT;
6306 switch (m) {11223 switch (m) {
...@@ -6327,11 +11244,12 @@ fn statFromPosix(st: *const posix.Stat) Io.File.Stat {...@@ -6327,11 +11244,12 @@ fn statFromPosix(st: *const posix.Stat) Io.File.Stat {
6327 };11244 };
6328}11245}
632911246
6330fn statFromWasi(st: *const std.os.wasi.filestat_t) Io.File.Stat {11247fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat {
6331 return .{11248 return .{
6332 .inode = st.ino,11249 .inode = st.ino,
11250 .nlink = st.nlink,
6333 .size = @bitCast(st.size),11251 .size = @bitCast(st.size),
6334 .mode = 0,11252 .permissions = .default_file,
6335 .kind = switch (st.filetype) {11253 .kind = switch (st.filetype) {
6336 .BLOCK_DEVICE => .block_device,11254 .BLOCK_DEVICE => .block_device,
6337 .CHARACTER_DEVICE => .character_device,11255 .CHARACTER_DEVICE => .character_device,
...@@ -6352,13 +11270,20 @@ fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp {...@@ -6352,13 +11270,20 @@ fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp {
6352}11270}
635311271
6354fn timestampToPosix(nanoseconds: i96) posix.timespec {11272fn 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 }
6355 return .{11280 return .{
6356 .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)),11281 .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)),
6357 .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)),11282 .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)),
6358 };11283 };
6359}11284}
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 {
6362 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;11287 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;
6363 // >= rather than > to make room for the null byte11288 // >= rather than > to make room for the null byte
6364 if (file_path.len >= buffer.len) return error.NameTooLong;11289 if (file_path.len >= buffer.len) return error.NameTooLong;
...@@ -6605,7 +11530,7 @@ fn lookupHosts(...@@ -6605,7 +11530,7 @@ fn lookupHosts(
6605 options: HostName.LookupOptions,11530 options: HostName.LookupOptions,
6606) !void {11531) !void {
6607 const t_io = io(t);11532 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) {
6609 error.FileNotFound,11534 error.FileNotFound,
6610 error.NotDir,11535 error.NotDir,
6611 error.AccessDenied,11536 error.AccessDenied,
...@@ -6809,6 +11734,506 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {...@@ -6809,6 +11734,506 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {
680911734
6810fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}11735fn 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
6812test {12237test {
6813 _ = @import("Threaded/test.zig");12238 _ = @import("Threaded/test.zig");
6814}12239}
lib/std/Io/Threaded/test.zig+7-5
...@@ -1,3 +1,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.
1const builtin = @import("builtin");3const builtin = @import("builtin");
24
3const std = @import("std");5const std = @import("std");
...@@ -11,7 +13,7 @@ test "concurrent vs main prevents deadlock via oversubscription" {...@@ -11,7 +13,7 @@ test "concurrent vs main prevents deadlock via oversubscription" {
11 return error.SkipZigTest;13 return error.SkipZigTest;
12 }14 }
1315
14 var threaded: Io.Threaded = .init(std.testing.allocator);16 var threaded: Io.Threaded = .init(std.testing.allocator, .{});
15 defer threaded.deinit();17 defer threaded.deinit();
16 const io = threaded.io();18 const io = threaded.io();
1719
...@@ -44,7 +46,7 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" {...@@ -44,7 +46,7 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" {
44 return error.SkipZigTest;46 return error.SkipZigTest;
45 }47 }
4648
47 var threaded: Io.Threaded = .init(std.testing.allocator);49 var threaded: Io.Threaded = .init(std.testing.allocator, .{});
48 defer threaded.deinit();50 defer threaded.deinit();
49 const io = threaded.io();51 const io = threaded.io();
5052
...@@ -78,7 +80,7 @@ test "async/concurrent context and result alignment" {...@@ -78,7 +80,7 @@ test "async/concurrent context and result alignment" {
78 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;80 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;
79 var fba: std.heap.FixedBufferAllocator = .init(&buffer);81 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(), .{});
82 defer threaded.deinit();84 defer threaded.deinit();
83 const io = threaded.io();85 const io = threaded.io();
8486
...@@ -111,7 +113,7 @@ test "Group.async context alignment" {...@@ -111,7 +113,7 @@ test "Group.async context alignment" {
111 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;113 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;
112 var fba: std.heap.FixedBufferAllocator = .init(&buffer);114 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(), .{});
115 defer threaded.deinit();117 defer threaded.deinit();
116 const io = threaded.io();118 const io = threaded.io();
117119
...@@ -131,7 +133,7 @@ fn returnArray() [32]u8 {...@@ -131,7 +133,7 @@ fn returnArray() [32]u8 {
131}133}
132134
133test "async with array return type" {135test "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, .{});
135 defer threaded.deinit();137 defer threaded.deinit();
136 const io = threaded.io();138 const io = threaded.io();
137139
lib/std/Io/Writer.zig+15-14
...@@ -1,7 +1,8 @@...@@ -1,7 +1,8 @@
1const Writer = @This();
2
1const builtin = @import("builtin");3const builtin = @import("builtin");
2const native_endian = builtin.target.cpu.arch.endian();4const native_endian = builtin.target.cpu.arch.endian();
35
4const Writer = @This();
5const std = @import("../std.zig");6const std = @import("../std.zig");
6const assert = std.debug.assert;7const assert = std.debug.assert;
7const Limit = std.Io.Limit;8const Limit = std.Io.Limit;
...@@ -960,7 +961,7 @@ pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllE...@@ -960,7 +961,7 @@ pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllE
960 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {961 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
961 error.EndOfStream => break,962 error.EndOfStream => break,
962 error.Unimplemented => {963 error.Unimplemented => {
963 file_reader.mode = file_reader.mode.toReading();964 file_reader.mode = file_reader.mode.toSimple();
964 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));965 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
965 break;966 break;
966 },967 },
...@@ -2834,14 +2835,14 @@ test "discarding sendFile" {...@@ -2834,14 +2835,14 @@ test "discarding sendFile" {
2834 var tmp_dir = testing.tmpDir(.{});2835 var tmp_dir = testing.tmpDir(.{});
2835 defer tmp_dir.cleanup();2836 defer tmp_dir.cleanup();
28362837
2837 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });2838 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2838 defer file.close();2839 defer file.close(io);
2839 var r_buffer: [256]u8 = undefined;2840 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);
2841 try file_writer.interface.writeByte('h');2842 try file_writer.interface.writeByte('h');
2842 try file_writer.interface.flush();2843 try file_writer.interface.flush();
28432844
2844 var file_reader = file_writer.moveToReader(io);2845 var file_reader = file_writer.moveToReader();
2845 try file_reader.seekTo(0);2846 try file_reader.seekTo(0);
28462847
2847 var w_buffer: [256]u8 = undefined;2848 var w_buffer: [256]u8 = undefined;
...@@ -2856,14 +2857,14 @@ test "allocating sendFile" {...@@ -2856,14 +2857,14 @@ test "allocating sendFile" {
2856 var tmp_dir = testing.tmpDir(.{});2857 var tmp_dir = testing.tmpDir(.{});
2857 defer tmp_dir.cleanup();2858 defer tmp_dir.cleanup();
28582859
2859 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });2860 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2860 defer file.close();2861 defer file.close(io);
2861 var r_buffer: [2]u8 = undefined;2862 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);
2863 try file_writer.interface.writeAll("abcd");2864 try file_writer.interface.writeAll("abcd");
2864 try file_writer.interface.flush();2865 try file_writer.interface.flush();
28652866
2866 var file_reader = file_writer.moveToReader(io);2867 var file_reader = file_writer.moveToReader();
2867 try file_reader.seekTo(0);2868 try file_reader.seekTo(0);
2868 try file_reader.interface.fill(2);2869 try file_reader.interface.fill(2);
28692870
...@@ -2880,14 +2881,14 @@ test sendFileReading {...@@ -2880,14 +2881,14 @@ test sendFileReading {
2880 var tmp_dir = testing.tmpDir(.{});2881 var tmp_dir = testing.tmpDir(.{});
2881 defer tmp_dir.cleanup();2882 defer tmp_dir.cleanup();
28822883
2883 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });2884 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2884 defer file.close();2885 defer file.close(io);
2885 var r_buffer: [2]u8 = undefined;2886 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);
2887 try file_writer.interface.writeAll("abcd");2888 try file_writer.interface.writeAll("abcd");
2888 try file_writer.interface.flush();2889 try file_writer.interface.flush();
28892890
2890 var file_reader = file_writer.moveToReader(io);2891 var file_reader = file_writer.moveToReader();
2891 try file_reader.seekTo(0);2892 try file_reader.seekTo(0);
2892 try file_reader.interface.fill(2);2893 try file_reader.interface.fill(2);
28932894
lib/std/Io/net.zig+22-3
...@@ -1043,7 +1043,11 @@ pub const Socket = struct {...@@ -1043,7 +1043,11 @@ pub const Socket = struct {
10431043
1044 /// Leaves `address` in a valid state.1044 /// Leaves `address` in a valid state.
1045 pub fn close(s: *const Socket, io: Io) void {1045 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);
1047 }1051 }
10481052
1049 pub const SendError = error{1053 pub const SendError = error{
...@@ -1184,7 +1188,7 @@ pub const Stream = struct {...@@ -1184,7 +1188,7 @@ pub const Stream = struct {
1184 const max_iovecs_len = 8;1188 const max_iovecs_len = 8;
11851189
1186 pub fn close(s: *const Stream, io: Io) void {1190 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]);
1188 }1192 }
11891193
1190 pub const Reader = struct {1194 pub const Reader = struct {
...@@ -1256,6 +1260,7 @@ pub const Stream = struct {...@@ -1256,6 +1260,7 @@ pub const Stream = struct {
1256 interface: Io.Writer,1260 interface: Io.Writer,
1257 stream: Stream,1261 stream: Stream,
1258 err: ?Error = null,1262 err: ?Error = null,
1263 write_file_err: ?WriteFileError = null,
12591264
1260 pub const Error = error{1265 pub const Error = error{
1261 /// Another TCP Fast Open is already in progress.1266 /// Another TCP Fast Open is already in progress.
...@@ -1285,12 +1290,19 @@ pub const Stream = struct {...@@ -1285,12 +1290,19 @@ pub const Stream = struct {
1285 SocketNotBound,1290 SocketNotBound,
1286 } || Io.UnexpectedError || Io.Cancelable;1291 } || Io.UnexpectedError || Io.Cancelable;
12871292
1293 pub const WriteFileError = error{
1294 NetworkDown,
1295 } || Io.Cancelable || Io.UnexpectedError;
1296
1288 pub fn init(stream: Stream, io: Io, buffer: []u8) Writer {1297 pub fn init(stream: Stream, io: Io, buffer: []u8) Writer {
1289 return .{1298 return .{
1290 .io = io,1299 .io = io,
1291 .stream = stream,1300 .stream = stream,
1292 .interface = .{1301 .interface = .{
1293 .vtable = &.{ .drain = drain },1302 .vtable = &.{
1303 .drain = drain,
1304 .sendFile = sendFile,
1305 },
1294 .buffer = buffer,1306 .buffer = buffer,
1295 },1307 },
1296 };1308 };
...@@ -1307,6 +1319,13 @@ pub const Stream = struct {...@@ -1307,6 +1319,13 @@ pub const Stream = struct {
1307 };1319 };
1308 return io_w.consume(n);1320 return io_w.consume(n);
1309 }1321 }
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 }
1310 };1329 };
13111330
1312 pub fn reader(stream: Stream, io: Io, buffer: []u8) Reader {1331 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 {...@@ -343,7 +343,7 @@ pub const ResolvConf = struct {
343 .attempts = 2,343 .attempts = 2,
344 };344 };
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) {
347 error.FileNotFound,347 error.FileNotFound,
348 error.NotDir,348 error.NotDir,
349 error.AccessDenied,349 error.AccessDenied,
lib/std/Io/net/test.zig+9-4
...@@ -232,8 +232,10 @@ test "listen on an in use port" {...@@ -232,8 +232,10 @@ test "listen on an in use port" {
232fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {232fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
233 if (builtin.os.tag == .wasi) return error.SkipZigTest;233 if (builtin.os.tag == .wasi) return error.SkipZigTest;
234234
235 const io = testing.io;
236
235 const connection = try net.tcpConnectToHost(allocator, name, port);237 const connection = try net.tcpConnectToHost(allocator, name, port);
236 defer connection.close();238 defer connection.close(io);
237239
238 var buf: [100]u8 = undefined;240 var buf: [100]u8 = undefined;
239 const len = try connection.read(&buf);241 const len = try connection.read(&buf);
...@@ -244,8 +246,10 @@ fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyer...@@ -244,8 +246,10 @@ fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyer
244fn testClient(addr: net.IpAddress) anyerror!void {246fn testClient(addr: net.IpAddress) anyerror!void {
245 if (builtin.os.tag == .wasi) return error.SkipZigTest;247 if (builtin.os.tag == .wasi) return error.SkipZigTest;
246248
249 const io = testing.io;
250
247 const socket_file = try net.tcpConnectToAddress(addr);251 const socket_file = try net.tcpConnectToAddress(addr);
248 defer socket_file.close();252 defer socket_file.close(io);
249253
250 var buf: [100]u8 = undefined;254 var buf: [100]u8 = undefined;
251 const len = try socket_file.read(&buf);255 const len = try socket_file.read(&buf);
...@@ -267,6 +271,7 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -267,6 +271,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
267 if (builtin.single_threaded) return error.SkipZigTest;271 if (builtin.single_threaded) return error.SkipZigTest;
268 if (!net.has_unix_sockets) return error.SkipZigTest;272 if (!net.has_unix_sockets) return error.SkipZigTest;
269 if (builtin.os.tag == .windows) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25983273 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
271 const io = testing.io;276 const io = testing.io;
272277
...@@ -274,7 +279,7 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -274,7 +279,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
274 defer testing.allocator.free(socket_path);279 defer testing.allocator.free(socket_path);
275280
276 const socket_addr = try net.UnixAddress.init(socket_path);281 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
279 var server = try socket_addr.listen(io, .{});284 var server = try socket_addr.listen(io, .{});
280 defer server.socket.close(io);285 defer server.socket.close(io);
...@@ -330,7 +335,7 @@ test "non-blocking tcp server" {...@@ -330,7 +335,7 @@ test "non-blocking tcp server" {
330 try testing.expectError(error.WouldBlock, accept_err);335 try testing.expectError(error.WouldBlock, accept_err);
331336
332 const socket_file = try net.tcpConnectToAddress(server.socket.address);337 const socket_file = try net.tcpConnectToAddress(server.socket.address);
333 defer socket_file.close();338 defer socket_file.close(io);
334339
335 var stream = try server.accept(io);340 var stream = try server.accept(io);
336 defer stream.close(io);341 defer stream.close(io);
lib/std/Io/test.zig+108-52
...@@ -3,16 +3,17 @@ const native_endian = builtin.cpu.arch.endian();...@@ -3,16 +3,17 @@ const native_endian = builtin.cpu.arch.endian();
33
4const std = @import("std");4const std = @import("std");
5const Io = std.Io;5const Io = std.Io;
6const testing = std.testing;
7const expect = std.testing.expect;
8const expectEqual = std.testing.expectEqual;
9const expectError = std.testing.expectError;
10const DefaultPrng = std.Random.DefaultPrng;6const DefaultPrng = std.Random.DefaultPrng;
11const mem = std.mem;7const mem = std.mem;
12const fs = std.fs;8const fs = std.fs;
13const File = std.fs.File;9const File = std.Io.File;
14const assert = std.debug.assert;10const 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;
16const tmpDir = std.testing.tmpDir;17const tmpDir = std.testing.tmpDir;
1718
18test "write a file, read it, then delete it" {19test "write a file, read it, then delete it" {
...@@ -27,10 +28,10 @@ test "write a file, read it, then delete it" {...@@ -27,10 +28,10 @@ test "write a file, read it, then delete it" {
27 random.bytes(data[0..]);28 random.bytes(data[0..]);
28 const tmp_file_name = "temp_test_file.txt";29 const tmp_file_name = "temp_test_file.txt";
29 {30 {
30 var file = try tmp.dir.createFile(tmp_file_name, .{});31 var file = try tmp.dir.createFile(io, tmp_file_name, .{});
31 defer file.close();32 defer file.close(io);
3233
33 var file_writer = file.writer(&.{});34 var file_writer = file.writer(io, &.{});
34 const st = &file_writer.interface;35 const st = &file_writer.interface;
35 try st.print("begin", .{});36 try st.print("begin", .{});
36 try st.writeAll(&data);37 try st.writeAll(&data);
...@@ -40,14 +41,14 @@ test "write a file, read it, then delete it" {...@@ -40,14 +41,14 @@ test "write a file, read it, then delete it" {
4041
41 {42 {
42 // Make sure the exclusive flag is honored.43 // 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 }));
44 }45 }
4546
46 {47 {
47 var file = try tmp.dir.openFile(tmp_file_name, .{});48 var file = try tmp.dir.openFile(io, tmp_file_name, .{});
48 defer file.close();49 defer file.close(io);
4950
50 const file_size = try file.getEndPos();51 const file_size = try file.length(io);
51 const expected_file_size: u64 = "begin".len + data.len + "end".len;52 const expected_file_size: u64 = "begin".len + data.len + "end".len;
52 try expectEqual(expected_file_size, file_size);53 try expectEqual(expected_file_size, file_size);
5354
...@@ -60,71 +61,126 @@ test "write a file, read it, then delete it" {...@@ -60,71 +61,126 @@ test "write a file, read it, then delete it" {
60 try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data));61 try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data));
61 try expect(mem.eql(u8, contents[contents.len - "end".len ..], "end"));62 try expect(mem.eql(u8, contents[contents.len - "end".len ..], "end"));
62 }63 }
63 try tmp.dir.deleteFile(tmp_file_name);64 try tmp.dir.deleteFile(io, tmp_file_name);
64}65}
6566
66test "File seek ops" {67test "File.Writer.seekTo" {
67 var tmp = tmpDir(.{});68 var tmp = tmpDir(.{});
68 defer tmp.cleanup();69 defer tmp.cleanup();
6970
71 const io = testing.io;
72
73 var data: [8192]u8 = undefined;
74 @memset(&data, 0x55);
75
70 const tmp_file_name = "temp_test_file.txt";76 const tmp_file_name = "temp_test_file.txt";
71 var file = try tmp.dir.createFile(tmp_file_name, .{});77 var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true });
72 defer file.close();78 defer file.close(io);
7379
74 try file.writeAll(&([_]u8{0x55} ** 8192));80 var fw = file.writerStreaming(io, &.{});
7581
76 // Seek to the end82 try fw.interface.writeAll(&data);
77 try file.seekFromEnd(0);83 try expect(fw.logicalPos() == try file.length(io));
78 try expect((try file.getPos()) == try file.getEndPos());84 try fw.seekTo(1234);
79 // Negative delta85 try expect(fw.logicalPos() == 1234);
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);
88}86}
8987
90test "setEndPos" {88test "File.setLength" {
89 const io = testing.io;
90
91 var tmp = tmpDir(.{});91 var tmp = tmpDir(.{});
92 defer tmp.cleanup();92 defer tmp.cleanup();
9393
94 const tmp_file_name = "temp_test_file.txt";94 const tmp_file_name = "temp_test_file.txt";
95 var file = try tmp.dir.createFile(tmp_file_name, .{});95 var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true });
96 defer file.close();96 defer file.close(io);
97
98 var fw = file.writerStreaming(io, &.{});
9799
98 // Verify that the file size changes and the file offset is not moved100 // Verify that the file size changes and the file offset is not moved
99 try expect((try file.getEndPos()) == 0);101 try expect((try file.length(io)) == 0);
100 try expect((try file.getPos()) == 0);102 try expect(fw.logicalPos() == 0);
101 try file.setEndPos(8192);103 try file.setLength(io, 8192);
102 try expect((try file.getEndPos()) == 8192);104 try expect((try file.length(io)) == 8192);
103 try expect((try file.getPos()) == 0);105 try expect(fw.logicalPos() == 0);
104 try file.seekTo(100);106 try fw.seekTo(100);
105 try file.setEndPos(4096);107 try file.setLength(io, 4096);
106 try expect((try file.getEndPos()) == 4096);108 try expect((try file.length(io)) == 4096);
107 try expect((try file.getPos()) == 100);109 try expect(fw.logicalPos() == 100);
108 try file.setEndPos(0);110 try file.setLength(io, 0);
109 try expect((try file.getEndPos()) == 0);111 try expect((try file.length(io)) == 0);
110 try expect((try file.getPos()) == 100);112 try expect(fw.logicalPos() == 100);
111}113}
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
114 var tmp = tmpDir(.{});169 var tmp = tmpDir(.{});
115 defer tmp.cleanup();170 defer tmp.cleanup();
116171
117 const tmp_file_name = "just_a_temporary_file.txt";172 const tmp_file_name = "just_a_temporary_file.txt";
118 var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true });173 var file = try tmp.dir.createFile(io, tmp_file_name, .{ .read = true });
119 defer file.close();174 defer file.close(io);
120175
121 const stat_old = try file.stat();176 const stat_old = try file.stat(io);
122 // Set atime and mtime to 5s before177 // Set atime and mtime to 5s before
123 try file.updateTimes(178 try file.setTimestamps(
179 io,
124 stat_old.atime.subDuration(.fromSeconds(5)),180 stat_old.atime.subDuration(.fromSeconds(5)),
125 stat_old.mtime.subDuration(.fromSeconds(5)),181 stat_old.mtime.subDuration(.fromSeconds(5)),
126 );182 );
127 const stat_new = try file.stat();183 const stat_new = try file.stat(io);
128 try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds);184 try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds);
129 try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);185 try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);
130}186}
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 @@...@@ -1,26 +1,30 @@
1//! This API is non-allocating, non-fallible, thread-safe, and lock-free.1//! This API is non-allocating, non-fallible, thread-safe, and lock-free.
2const Progress = @This();
23
3const std = @import("std");
4const builtin = @import("builtin");4const 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;
5const windows = std.os.windows;10const windows = std.os.windows;
6const testing = std.testing;11const testing = std.testing;
7const assert = std.debug.assert;12const assert = std.debug.assert;
8const Progress = @This();
9const posix = std.posix;13const posix = std.posix;
10const is_big_endian = builtin.cpu.arch.endian() == .big;
11const is_windows = builtin.os.tag == .windows;
12const Writer = std.Io.Writer;14const Writer = std.Io.Writer;
1315
14/// `null` if the current node (and its children) should16/// Currently this API only supports this value being set to stderr, which
15/// not print on update()17/// happens automatically inside `start`.
16terminal: std.fs.File,18terminal: Io.File,
19
20io: Io,
1721
18terminal_mode: TerminalMode,22terminal_mode: TerminalMode,
1923
20update_thread: ?std.Thread,24update_worker: ?Io.Future(void),
2125
22/// Atomically set by SIGWINCH as well as the root done() function.26/// Atomically set by SIGWINCH as well as the root done() function.
23redraw_event: std.Thread.ResetEvent,27redraw_event: Io.Event,
24/// Indicates a request to shut down and reset global state.28/// Indicates a request to shut down and reset global state.
25/// Accessed atomically.29/// Accessed atomically.
26done: bool,30done: bool,
...@@ -48,6 +52,8 @@ node_freelist: Freelist,...@@ -48,6 +52,8 @@ node_freelist: Freelist,
48/// value may at times temporarily exceed the node count.52/// value may at times temporarily exceed the node count.
49node_end_index: u32,53node_end_index: u32,
5054
55start_failure: StartFailure,
56
51pub const Status = enum {57pub const Status = enum {
52 /// Indicates the application is progressing towards completion of a task.58 /// Indicates the application is progressing towards completion of a task.
53 /// Unless the application is interactive, this is the only status the59 /// Unless the application is interactive, this is the only status the
...@@ -93,9 +99,9 @@ pub const Options = struct {...@@ -93,9 +99,9 @@ pub const Options = struct {
93 /// Must be at least 200 bytes.99 /// Must be at least 200 bytes.
94 draw_buffer: []u8 = &default_draw_buffer,100 draw_buffer: []u8 = &default_draw_buffer,
95 /// How many nanoseconds between writing updates to the terminal.101 /// 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),
97 /// How many nanoseconds to keep the output hidden103 /// 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),
99 /// If provided, causes the progress item to have a denominator.105 /// If provided, causes the progress item to have a denominator.
100 /// 0 means unknown.106 /// 0 means unknown.
101 estimated_total_items: usize = 0,107 estimated_total_items: usize = 0,
...@@ -121,20 +127,20 @@ pub const Node = struct {...@@ -121,20 +127,20 @@ pub const Node = struct {
121 name: [max_name_len]u8 align(@alignOf(usize)),127 name: [max_name_len]u8 align(@alignOf(usize)),
122128
123 /// Not thread-safe.129 /// Not thread-safe.
124 fn getIpcFd(s: Storage) ?posix.fd_t {130 fn getIpcFd(s: Storage) ?Io.File.Handle {
125 return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(posix.fd_t)) {131 return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) {
126 .int => @bitCast(s.completed_count),132 .int => @bitCast(s.completed_count),
127 .pointer => @ptrFromInt(s.completed_count),133 .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)),
129 } else null;135 } else null;
130 }136 }
131137
132 /// Thread-safe.138 /// Thread-safe.
133 fn setIpcFd(s: *Storage, fd: posix.fd_t) void {139 fn setIpcFd(s: *Storage, fd: Io.File.Handle) void {
134 const integer: u32 = switch (@typeInfo(posix.fd_t)) {140 const integer: u32 = switch (@typeInfo(Io.File.Handle)) {
135 .int => @bitCast(fd),141 .int => @bitCast(fd),
136 .pointer => @intFromPtr(fd),142 .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)),
138 };144 };
139 // `estimated_total_count` max int indicates the special state that145 // `estimated_total_count` max int indicates the special state that
140 // causes `completed_count` to be treated as a file descriptor, so146 // causes `completed_count` to be treated as a file descriptor, so
...@@ -327,13 +333,14 @@ pub const Node = struct {...@@ -327,13 +333,14 @@ pub const Node = struct {
327 }333 }
328 } else {334 } else {
329 @atomicStore(bool, &global_progress.done, true, .monotonic);335 @atomicStore(bool, &global_progress.done, true, .monotonic);
330 global_progress.redraw_event.set();336 const io = global_progress.io;
331 if (global_progress.update_thread) |thread| thread.join();337 global_progress.redraw_event.set(io);
338 if (global_progress.update_worker) |*worker| worker.await(io);
332 }339 }
333 }340 }
334341
335 /// Posix-only. Used by `std.process.Child`. Thread-safe.342 /// 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 {
337 const index = node.index.unwrap() orelse return;344 const index = node.index.unwrap() orelse return;
338 assert(fd >= 0);345 assert(fd >= 0);
339 assert(fd != posix.STDOUT_FILENO);346 assert(fd != posix.STDOUT_FILENO);
...@@ -344,14 +351,14 @@ pub const Node = struct {...@@ -344,14 +351,14 @@ pub const Node = struct {
344351
345 /// Posix-only. Thread-safe. Assumes the node is storing an IPC file352 /// Posix-only. Thread-safe. Assumes the node is storing an IPC file
346 /// descriptor.353 /// descriptor.
347 pub fn getIpcFd(node: Node) ?posix.fd_t {354 pub fn getIpcFd(node: Node) ?Io.File.Handle {
348 const index = node.index.unwrap() orelse return null;355 const index = node.index.unwrap() orelse return null;
349 const storage = storageByIndex(index);356 const storage = storageByIndex(index);
350 const int = @atomicLoad(u32, &storage.completed_count, .monotonic);357 const int = @atomicLoad(u32, &storage.completed_count, .monotonic);
351 return switch (@typeInfo(posix.fd_t)) {358 return switch (@typeInfo(Io.File.Handle)) {
352 .int => @bitCast(int),359 .int => @bitCast(int),
353 .pointer => @ptrFromInt(int),360 .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)),
355 };362 };
356 }363 }
357364
...@@ -389,9 +396,10 @@ pub const Node = struct {...@@ -389,9 +396,10 @@ pub const Node = struct {
389};396};
390397
391var global_progress: Progress = .{398var global_progress: Progress = .{
399 .io = undefined,
392 .terminal = undefined,400 .terminal = undefined,
393 .terminal_mode = .off,401 .terminal_mode = .off,
394 .update_thread = null,402 .update_worker = null,
395 .redraw_event = .unset,403 .redraw_event = .unset,
396 .refresh_rate_ns = undefined,404 .refresh_rate_ns = undefined,
397 .initial_delay_ns = undefined,405 .initial_delay_ns = undefined,
...@@ -401,6 +409,7 @@ var global_progress: Progress = .{...@@ -401,6 +409,7 @@ var global_progress: Progress = .{
401 .done = false,409 .done = false,
402 .need_clear = false,410 .need_clear = false,
403 .status = .working,411 .status = .working,
412 .start_failure = .unstarted,
404413
405 .node_parents = &node_parents_buffer,414 .node_parents = &node_parents_buffer,
406 .node_storage = &node_storage_buffer,415 .node_storage = &node_storage_buffer,
...@@ -409,6 +418,13 @@ var global_progress: Progress = .{...@@ -409,6 +418,13 @@ var global_progress: Progress = .{
409 .node_end_index = 0,418 .node_end_index = 0,
410};419};
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
412const node_storage_buffer_len = 83;428const node_storage_buffer_len = 83;
413var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;429var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;
414var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;430var 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) {...@@ -435,7 +451,9 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) {
435/// Asserts there is only one global Progress instance.451/// Asserts there is only one global Progress instance.
436///452///
437/// Call `Node.end` when done.453/// 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 {
439 // Ensure there is only 1 global Progress object.457 // Ensure there is only 1 global Progress object.
440 if (global_progress.node_end_index != 0) {458 if (global_progress.node_end_index != 0) {
441 debug_start_trace.dump();459 debug_start_trace.dump();
...@@ -450,21 +468,24 @@ pub fn start(options: Options) Node {...@@ -450,21 +468,24 @@ pub fn start(options: Options) Node {
450468
451 assert(options.draw_buffer.len >= 200);469 assert(options.draw_buffer.len >= 200);
452 global_progress.draw_buffer = options.draw_buffer;470 global_progress.draw_buffer = options.draw_buffer;
453 global_progress.refresh_rate_ns = options.refresh_rate_ns;471 global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds());
454 global_progress.initial_delay_ns = options.initial_delay_ns;472 global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds());
455473
456 if (noop_impl)474 if (noop_impl)
457 return Node.none;475 return Node.none;
458476
477 global_progress.io = io;
478
459 if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| {479 if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| {
460 global_progress.update_thread = std.Thread.spawn(.{}, ipcThreadRun, .{480 global_progress.update_worker = io.concurrent(ipcThreadRun, .{
461 @as(posix.fd_t, switch (@typeInfo(posix.fd_t)) {481 io,
482 @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) {
462 .int => ipc_fd,483 .int => ipc_fd,
463 .pointer => @ptrFromInt(ipc_fd),484 .pointer => @ptrFromInt(ipc_fd),
464 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),485 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
465 }),486 } }),
466 }) catch |err| {487 }) 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 };
468 return Node.none;489 return Node.none;
469 };490 };
470 } else |env_err| switch (env_err) {491 } else |env_err| switch (env_err) {
...@@ -472,14 +493,21 @@ pub fn start(options: Options) Node {...@@ -472,14 +493,21 @@ pub fn start(options: Options) Node {
472 if (options.disable_printing) {493 if (options.disable_printing) {
473 return Node.none;494 return Node.none;
474 }495 }
475 const stderr: std.fs.File = .stderr();496 const stderr: Io.File = .stderr();
476 global_progress.terminal = stderr;497 global_progress.terminal = stderr;
477 if (stderr.getOrEnableAnsiEscapeSupport()) {498 if (stderr.enableAnsiEscapeCodes(io)) |_| {
478 global_progress.terminal_mode = .ansi_escape_codes;499 global_progress.terminal_mode = .ansi_escape_codes;
479 } else if (is_windows and stderr.isTty()) {500 } else |_| if (is_windows) {
480 global_progress.terminal_mode = TerminalMode{ .windows_api = .{501 if (stderr.isTty(io)) |is_tty| {
481 .code_page = windows.kernel32.GetConsoleOutputCP(),502 if (is_tty) global_progress.terminal_mode = TerminalMode{ .windows_api = .{
482 } };503 .code_page = windows.kernel32.GetConsoleOutputCP(),
504 } };
505 } else |err| switch (err) {
506 error.Canceled => {
507 io.recancel();
508 return Node.none;
509 },
510 }
483 }511 }
484512
485 if (global_progress.terminal_mode == .off) {513 if (global_progress.terminal_mode == .off) {
...@@ -497,17 +525,17 @@ pub fn start(options: Options) Node {...@@ -497,17 +525,17 @@ pub fn start(options: Options) Node {
497525
498 if (switch (global_progress.terminal_mode) {526 if (switch (global_progress.terminal_mode) {
499 .off => unreachable, // handled a few lines above527 .off => unreachable, // handled a few lines above
500 .ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{}),528 .ansi_escape_codes => io.concurrent(updateThreadRun, .{io}),
501 .windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{}) else unreachable,529 .windows_api => if (is_windows) io.concurrent(windowsApiUpdateThreadRun, .{io}) else unreachable,
502 }) |thread| {530 }) |future| {
503 global_progress.update_thread = thread;531 global_progress.update_worker = future;
504 } else |err| {532 } 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 };
506 return Node.none;534 return Node.none;
507 }535 }
508 },536 },
509 else => |e| {537 else => |e| {
510 std.log.warn("invalid ZIG_PROGRESS file descriptor integer: {s}", .{@errorName(e)});538 global_progress.start_failure = .{ .parse_env_var = e };
511 return Node.none;539 return Node.none;
512 },540 },
513 }541 }
...@@ -521,48 +549,52 @@ pub fn setStatus(new_status: Status) void {...@@ -521,48 +549,52 @@ pub fn setStatus(new_status: Status) void {
521}549}
522550
523/// Returns whether a resize is needed to learn the terminal size.551/// Returns whether a resize is needed to learn the terminal size.
524fn wait(timeout_ns: u64) bool {552fn wait(io: Io, timeout_ns: u64) bool {
525 const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| true else |err| switch (err) {553 const timeout: Io.Timeout = .{ .duration = .{
526 error.Timeout => false,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,
527 };559 };
528 global_progress.redraw_event.reset();560 global_progress.redraw_event.reset();
529 return resize_flag or (global_progress.cols == 0);561 return resize_flag or (global_progress.cols == 0);
530}562}
531563
532fn updateThreadRun() void {564fn updateThreadRun(io: Io) void {
533 // Store this data in the thread so that it does not need to be part of the565 // Store this data in the thread so that it does not need to be part of the
534 // linker data of the main executable.566 // linker data of the main executable.
535 var serialized_buffer: Serialized.Buffer = undefined;567 var serialized_buffer: Serialized.Buffer = undefined;
536568
537 {569 {
538 const resize_flag = wait(global_progress.initial_delay_ns);570 const resize_flag = wait(io, global_progress.initial_delay_ns);
539 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;571 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
540 maybeUpdateSize(resize_flag);572 maybeUpdateSize(resize_flag);
541573
542 const buffer, _ = computeRedraw(&serialized_buffer);574 const buffer, _ = computeRedraw(&serialized_buffer);
543 if (stderr_mutex.tryLock()) {575 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
544 defer stderr_mutex.unlock();576 defer io.unlockStderr();
545 write(buffer) catch return;
546 global_progress.need_clear = true;577 global_progress.need_clear = true;
578 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
547 }579 }
548 }580 }
549581
550 while (true) {582 while (true) {
551 const resize_flag = wait(global_progress.refresh_rate_ns);583 const resize_flag = wait(io, global_progress.refresh_rate_ns);
552584
553 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {585 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
554 stderr_mutex.lock();586 const stderr = io.lockStderr(&.{}, null) catch return;
555 defer stderr_mutex.unlock();587 defer io.unlockStderr();
556 return clearWrittenWithEscapeCodes() catch {};588 return clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
557 }589 }
558590
559 maybeUpdateSize(resize_flag);591 maybeUpdateSize(resize_flag);
560592
561 const buffer, _ = computeRedraw(&serialized_buffer);593 const buffer, _ = computeRedraw(&serialized_buffer);
562 if (stderr_mutex.tryLock()) {594 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
563 defer stderr_mutex.unlock();595 defer io.unlockStderr();
564 write(buffer) catch return;
565 global_progress.need_clear = true;596 global_progress.need_clear = true;
597 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
566 }598 }
567 }599 }
568}600}
...@@ -575,117 +607,72 @@ fn windowsApiWriteMarker() void {...@@ -575,117 +607,72 @@ fn windowsApiWriteMarker() void {
575 _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null);607 _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null);
576}608}
577609
578fn windowsApiUpdateThreadRun() void {610fn windowsApiUpdateThreadRun(io: Io) void {
579 var serialized_buffer: Serialized.Buffer = undefined;611 var serialized_buffer: Serialized.Buffer = undefined;
580612
581 {613 {
582 const resize_flag = wait(global_progress.initial_delay_ns);614 const resize_flag = wait(io, global_progress.initial_delay_ns);
583 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;615 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
584 maybeUpdateSize(resize_flag);616 maybeUpdateSize(resize_flag);
585617
586 const buffer, const nl_n = computeRedraw(&serialized_buffer);618 const buffer, const nl_n = computeRedraw(&serialized_buffer);
587 if (stderr_mutex.tryLock()) {619 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
588 defer stderr_mutex.unlock();620 defer io.unlockStderr();
589 windowsApiWriteMarker();621 windowsApiWriteMarker();
590 write(buffer) catch return;
591 global_progress.need_clear = true;622 global_progress.need_clear = true;
623 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
592 windowsApiMoveToMarker(nl_n) catch return;624 windowsApiMoveToMarker(nl_n) catch return;
593 }625 }
594 }626 }
595627
596 while (true) {628 while (true) {
597 const resize_flag = wait(global_progress.refresh_rate_ns);629 const resize_flag = wait(io, global_progress.refresh_rate_ns);
598630
599 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {631 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
600 stderr_mutex.lock();632 _ = io.lockStderr(&.{}, null) catch return;
601 defer stderr_mutex.unlock();633 defer io.unlockStderr();
602 return clearWrittenWindowsApi() catch {};634 return clearWrittenWindowsApi() catch {};
603 }635 }
604636
605 maybeUpdateSize(resize_flag);637 maybeUpdateSize(resize_flag);
606638
607 const buffer, const nl_n = computeRedraw(&serialized_buffer);639 const buffer, const nl_n = computeRedraw(&serialized_buffer);
608 if (stderr_mutex.tryLock()) {640 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
609 defer stderr_mutex.unlock();641 defer io.unlockStderr();
610 clearWrittenWindowsApi() catch return;642 clearWrittenWindowsApi() catch return;
611 windowsApiWriteMarker();643 windowsApiWriteMarker();
612 write(buffer) catch return;
613 global_progress.need_clear = true;644 global_progress.need_clear = true;
645 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
614 windowsApiMoveToMarker(nl_n) catch return;646 windowsApiMoveToMarker(nl_n) catch return;
615 }647 }
616 }648 }
617}649}
618650
619/// Allows the caller to freely write to stderr until `unlockStdErr` is called.651fn ipcThreadRun(io: Io, file: Io.File) void {
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 {
665 // Store this data in the thread so that it does not need to be part of the652 // Store this data in the thread so that it does not need to be part of the
666 // linker data of the main executable.653 // linker data of the main executable.
667 var serialized_buffer: Serialized.Buffer = undefined;654 var serialized_buffer: Serialized.Buffer = undefined;
668655
669 {656 {
670 _ = wait(global_progress.initial_delay_ns);657 _ = wait(io, global_progress.initial_delay_ns);
671658
672 if (@atomicLoad(bool, &global_progress.done, .monotonic))659 if (@atomicLoad(bool, &global_progress.done, .monotonic))
673 return;660 return;
674661
675 const serialized = serialize(&serialized_buffer);662 const serialized = serialize(&serialized_buffer);
676 writeIpc(fd, serialized) catch |err| switch (err) {663 writeIpc(io, file, serialized) catch |err| switch (err) {
677 error.BrokenPipe => return,664 error.BrokenPipe => return,
678 };665 };
679 }666 }
680667
681 while (true) {668 while (true) {
682 _ = wait(global_progress.refresh_rate_ns);669 _ = wait(io, global_progress.refresh_rate_ns);
683670
684 if (@atomicLoad(bool, &global_progress.done, .monotonic))671 if (@atomicLoad(bool, &global_progress.done, .monotonic))
685 return;672 return;
686673
687 const serialized = serialize(&serialized_buffer);674 const serialized = serialize(&serialized_buffer);
688 writeIpc(fd, serialized) catch |err| switch (err) {675 writeIpc(io, file, serialized) catch |err| switch (err) {
689 error.BrokenPipe => return,676 error.BrokenPipe => return,
690 };677 };
691 }678 }
...@@ -784,11 +771,10 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {...@@ -784,11 +771,10 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
784 }771 }
785}772}
786773
787fn clearWrittenWithEscapeCodes() anyerror!void {774pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error!void {
788 if (noop_impl or !global_progress.need_clear) return;775 if (noop_impl or !global_progress.need_clear) return;
789776 try file_writer.interface.writeAll(clear ++ progress_remove);
790 global_progress.need_clear = false;777 global_progress.need_clear = false;
791 try write(clear ++ progress_remove);
792}778}
793779
794/// U+25BA or ►780/// U+25BA or ►
...@@ -948,11 +934,11 @@ const SavedMetadata = struct {...@@ -948,11 +934,11 @@ const SavedMetadata = struct {
948const Fd = enum(i32) {934const Fd = enum(i32) {
949 _,935 _,
950936
951 fn init(fd: posix.fd_t) Fd {937 fn init(fd: Io.File.Handle) Fd {
952 return @enumFromInt(if (is_windows) @as(isize, @bitCast(@intFromPtr(fd))) else fd);938 return @enumFromInt(if (is_windows) @as(isize, @bitCast(@intFromPtr(fd))) else fd);
953 }939 }
954940
955 fn get(fd: Fd) posix.fd_t {941 fn get(fd: Fd) Io.File.Handle {
956 return if (is_windows)942 return if (is_windows)
957 @ptrFromInt(@as(usize, @bitCast(@as(isize, @intFromEnum(fd)))))943 @ptrFromInt(@as(usize, @bitCast(@as(isize, @intFromEnum(fd)))))
958 else944 else
...@@ -963,6 +949,7 @@ const Fd = enum(i32) {...@@ -963,6 +949,7 @@ const Fd = enum(i32) {
963var ipc_metadata_len: u8 = 0;949var ipc_metadata_len: u8 = 0;
964950
965fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize {951fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize {
952 const io = global_progress.io;
966 const ipc_metadata_fds_copy = &serialized_buffer.ipc_metadata_fds_copy;953 const ipc_metadata_fds_copy = &serialized_buffer.ipc_metadata_fds_copy;
967 const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy;954 const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy;
968 const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds;955 const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds;
...@@ -981,14 +968,14 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -981,14 +968,14 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
981 0..,968 0..,
982 ) |main_parent, *main_storage, main_index| {969 ) |main_parent, *main_storage, main_index| {
983 if (main_parent == .unused) continue;970 if (main_parent == .unused) continue;
984 const fd = main_storage.getIpcFd() orelse continue;971 const file: Io.File = .{ .handle = main_storage.getIpcFd() orelse continue };
985 const opt_saved_metadata = findOld(fd, old_ipc_metadata_fds, old_ipc_metadata);972 const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
986 var bytes_read: usize = 0;973 var bytes_read: usize = 0;
987 while (true) {974 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) {
989 error.WouldBlock => break,976 error.WouldBlock => break,
990 else => |e| {977 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});
992 main_storage.completed_count = 0;979 main_storage.completed_count = 0;
993 main_storage.estimated_total_count = 0;980 main_storage.estimated_total_count = 0;
994 continue :main_loop;981 continue :main_loop;
...@@ -1014,7 +1001,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -1014,7 +1001,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
1014 // Ignore all but the last message on the pipe.1001 // Ignore all but the last message on the pipe.
1015 var input: []u8 = pipe_buf[0..bytes_read];1002 var input: []u8 = pipe_buf[0..bytes_read];
1016 if (input.len == 0) {1003 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);
1018 continue;1005 continue;
1019 }1006 }
10201007
...@@ -1024,7 +1011,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -1024,7 +1011,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
1024 if (input.len < expected_bytes) {1011 if (input.len < expected_bytes) {
1025 // Ignore short reads. We'll handle the next full message when it comes instead.1012 // Ignore short reads. We'll handle the next full message when it comes instead.
1026 const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len);1013 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);
1028 continue :main_loop;1015 continue :main_loop;
1029 }1016 }
1030 if (input.len > expected_bytes) {1017 if (input.len > expected_bytes) {
...@@ -1042,7 +1029,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -1042,7 +1029,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
1042 const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len));1029 const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len));
10431030
1044 // Remember in case the pipe is empty on next update.1031 // 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);
1046 ipc_metadata[ipc_metadata_len] = .{1033 ipc_metadata[ipc_metadata_len] = .{
1047 .remaining_read_trash_bytes = 0,1034 .remaining_read_trash_bytes = 0,
1048 .start_index = @intCast(serialized_len),1035 .start_index = @intCast(serialized_len),
...@@ -1100,7 +1087,7 @@ fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void {...@@ -1100,7 +1087,7 @@ fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void {
1100}1087}
11011088
1102fn findOld(1089fn findOld(
1103 ipc_fd: posix.fd_t,1090 ipc_fd: Io.File.Handle,
1104 old_metadata_fds: []Fd,1091 old_metadata_fds: []Fd,
1105 old_metadata: []SavedMetadata,1092 old_metadata: []SavedMetadata,
1106) ?*SavedMetadata {1093) ?*SavedMetadata {
...@@ -1118,7 +1105,7 @@ fn useSavedIpcData(...@@ -1118,7 +1105,7 @@ fn useSavedIpcData(
1118 main_index: usize,1105 main_index: usize,
1119 opt_saved_metadata: ?*SavedMetadata,1106 opt_saved_metadata: ?*SavedMetadata,
1120 remaining_read_trash_bytes: u16,1107 remaining_read_trash_bytes: u16,
1121 fd: posix.fd_t,1108 fd: Io.File.Handle,
1122) usize {1109) usize {
1123 const parents_copy = &serialized_buffer.parents_copy;1110 const parents_copy = &serialized_buffer.parents_copy;
1124 const storage_copy = &serialized_buffer.storage_copy;1111 const storage_copy = &serialized_buffer.storage_copy;
...@@ -1415,13 +1402,9 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {...@@ -1415,13 +1402,9 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {
1415 return nl_n + 2 < p.rows;1402 return nl_n + 2 < p.rows;
1416}1403}
14171404
1418fn write(buf: []const u8) anyerror!void {
1419 try global_progress.terminal.writeAll(buf);
1420}
1421
1422var remaining_write_trash_bytes: usize = 0;1405var 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 {
1425 // Byteswap if necessary to ensure little endian over the pipe. This is1408 // Byteswap if necessary to ensure little endian over the pipe. This is
1426 // needed because the parent or child process might be running in qemu.1409 // needed because the parent or child process might be running in qemu.
1427 if (is_big_endian) for (serialized.storage) |*s| s.byteSwap();1410 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 {...@@ -1432,11 +1415,7 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {
1432 const storage = std.mem.sliceAsBytes(serialized.storage);1415 const storage = std.mem.sliceAsBytes(serialized.storage);
1433 const parents = std.mem.sliceAsBytes(serialized.parents);1416 const parents = std.mem.sliceAsBytes(serialized.parents);
14341417
1435 var vecs: [3]posix.iovec_const = .{1418 var vecs: [3][]const u8 = .{ header, storage, parents };
1436 .{ .base = header.ptr, .len = header.len },
1437 .{ .base = storage.ptr, .len = storage.len },
1438 .{ .base = parents.ptr, .len = parents.len },
1439 };
14401419
1441 // Ensures the packet can fit in the pipe buffer.1420 // Ensures the packet can fit in the pipe buffer.
1442 const upper_bound_msg_len = 1 + node_storage_buffer_len * @sizeOf(Node.Storage) +1421 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 {...@@ -1447,14 +1426,14 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {
1447 // We do this in a separate write call to give a better chance for the1426 // We do this in a separate write call to give a better chance for the
1448 // writev below to be in a single packet.1427 // writev below to be in a single packet.
1449 const n = @min(parents.len, remaining_write_trash_bytes);1428 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| {
1451 remaining_write_trash_bytes -= written;1430 remaining_write_trash_bytes -= written;
1452 continue;1431 continue;
1453 } else |err| switch (err) {1432 } else |err| switch (err) {
1454 error.WouldBlock => return,1433 error.WouldBlock => return,
1455 error.BrokenPipe => return error.BrokenPipe,1434 error.BrokenPipe => return error.BrokenPipe,
1456 else => |e| {1435 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});
1458 return error.BrokenPipe;1437 return error.BrokenPipe;
1459 },1438 },
1460 }1439 }
...@@ -1462,7 +1441,7 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {...@@ -1462,7 +1441,7 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {
14621441
1463 // If this write would block we do not want to keep trying, but we need to1442 // If this write would block we do not want to keep trying, but we need to
1464 // know if a partial message was written.1443 // know if a partial message was written.
1465 if (writevNonblock(fd, &vecs)) |written| {1444 if (writevNonblock(io, file, &vecs)) |written| {
1466 const total = header.len + storage.len + parents.len;1445 const total = header.len + storage.len + parents.len;
1467 if (written < total) {1446 if (written < total) {
1468 remaining_write_trash_bytes = total - written;1447 remaining_write_trash_bytes = total - written;
...@@ -1471,13 +1450,13 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {...@@ -1471,13 +1450,13 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) error{BrokenPipe}!void {
1471 error.WouldBlock => {},1450 error.WouldBlock => {},
1472 error.BrokenPipe => return error.BrokenPipe,1451 error.BrokenPipe => return error.BrokenPipe,
1473 else => |e| {1452 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});
1475 return error.BrokenPipe;1454 return error.BrokenPipe;
1476 },1455 },
1477 }1456 }
1478}1457}
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 {
1481 var iov_index: usize = 0;1460 var iov_index: usize = 0;
1482 var written: usize = 0;1461 var written: usize = 0;
1483 var total_written: usize = 0;1462 var total_written: usize = 0;
...@@ -1486,9 +1465,9 @@ fn writevNonblock(fd: posix.fd_t, iov: []posix.iovec_const) posix.WriteError!usi...@@ -1486,9 +1465,9 @@ fn writevNonblock(fd: posix.fd_t, iov: []posix.iovec_const) posix.WriteError!usi
1486 written >= iov[iov_index].len1465 written >= iov[iov_index].len
1487 else1466 else
1488 return total_written) : (iov_index += 1) written -= iov[iov_index].len;1467 return total_written) : (iov_index += 1) written -= iov[iov_index].len;
1489 iov[iov_index].base += written;1468 iov[iov_index].ptr += written;
1490 iov[iov_index].len -= written;1469 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);
1492 if (written == 0) return total_written;1471 if (written == 0) return total_written;
1493 total_written += written;1472 total_written += written;
1494 }1473 }
...@@ -1538,7 +1517,7 @@ fn handleSigWinch(sig: posix.SIG, info: *const posix.siginfo_t, ctx_ptr: ?*anyop...@@ -1538,7 +1517,7 @@ fn handleSigWinch(sig: posix.SIG, info: *const posix.siginfo_t, ctx_ptr: ?*anyop
1538 _ = info;1517 _ = info;
1539 _ = ctx_ptr;1518 _ = ctx_ptr;
1540 assert(sig == .WINCH);1519 assert(sig == .WINCH);
1541 global_progress.redraw_event.set();1520 global_progress.redraw_event.set(global_progress.io);
1542}1521}
15431522
1544const have_sigwinch = switch (builtin.os.tag) {1523const have_sigwinch = switch (builtin.os.tag) {
...@@ -1563,11 +1542,6 @@ const have_sigwinch = switch (builtin.os.tag) {...@@ -1563,11 +1542,6 @@ const have_sigwinch = switch (builtin.os.tag) {
1563 else => false,1542 else => false,
1564};1543};
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
1571fn copyAtomicStore(dest: []align(@alignOf(usize)) u8, src: []const u8) void {1545fn copyAtomicStore(dest: []align(@alignOf(usize)) u8, src: []const u8) void {
1572 assert(dest.len == src.len);1546 assert(dest.len == src.len);
1573 const chunked_len = dest.len / @sizeOf(usize);1547 const chunked_len = dest.len / @sizeOf(usize);
lib/std/Random/benchmark.zig+4-2
...@@ -1,7 +1,9 @@...@@ -1,7 +1,9 @@
1// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig1// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
22
3const std = @import("std");
4const builtin = @import("builtin");3const builtin = @import("builtin");
4
5const std = @import("std");
6const Io = std.Io;
5const time = std.time;7const time = std.time;
6const Timer = time.Timer;8const Timer = time.Timer;
7const Random = std.Random;9const Random = std.Random;
...@@ -123,7 +125,7 @@ fn mode(comptime x: comptime_int) comptime_int {...@@ -123,7 +125,7 @@ fn mode(comptime x: comptime_int) comptime_int {
123125
124pub fn main() !void {126pub fn main() !void {
125 var stdout_buffer: [0x100]u8 = undefined;127 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);
127 const stdout = &stdout_writer.interface;129 const stdout = &stdout_writer.interface;
128130
129 var buffer: [1024]u8 = undefined;131 var buffer: [1024]u8 = undefined;
lib/std/Thread.zig+19-17
...@@ -7,6 +7,7 @@ const target = builtin.target;...@@ -7,6 +7,7 @@ const target = builtin.target;
7const native_os = builtin.os.tag;7const native_os = builtin.os.tag;
88
9const std = @import("std.zig");9const std = @import("std.zig");
10const Io = std.Io;
10const math = std.math;11const math = std.math;
11const assert = std.debug.assert;12const assert = std.debug.assert;
12const posix = std.posix;13const posix = std.posix;
...@@ -174,9 +175,9 @@ pub const SetNameError = error{...@@ -174,9 +175,9 @@ pub const SetNameError = error{
174 Unsupported,175 Unsupported,
175 Unexpected,176 Unexpected,
176 InvalidWtf8,177 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 {
180 if (name.len > max_name_len) return error.NameTooLong;181 if (name.len > max_name_len) return error.NameTooLong;
181182
182 const name_with_terminator = blk: {183 const name_with_terminator = blk: {
...@@ -207,10 +208,10 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {...@@ -207,10 +208,10 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
207 var buf: [32]u8 = undefined;208 var buf: [32]u8 = undefined;
208 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});209 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 const file = try Io.Dir.cwd().openFile(io, path, .{ .mode = .write_only });
211 defer file.close();212 defer file.close(io);
212213
213 try file.writeAll(name);214 try file.writeStreamingAll(io, name);
214 return;215 return;
215 },216 },
216 .windows => {217 .windows => {
...@@ -292,7 +293,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {...@@ -292,7 +293,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
292pub const GetNameError = error{293pub const GetNameError = error{
293 Unsupported,294 Unsupported,
294 Unexpected,295 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
297/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).298/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
298/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.299/// 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...@@ -321,11 +322,10 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
321 var buf: [32]u8 = undefined;322 var buf: [32]u8 = undefined;
322 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});323 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 = std.Options.debug_io;
325 const io = threaded.ioBasic();
326326
327 const file = try std.fs.cwd().openFile(path, .{});327 const file = try Io.Dir.cwd().openFile(io, path, .{});
328 defer file.close();328 defer file.close(io);
329329
330 var file_reader = file.readerStreaming(io, &.{});330 var file_reader = file.readerStreaming(io, &.{});
331 const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) {331 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 {...@@ -1675,14 +1675,14 @@ const LinuxThreadImpl = struct {
1675 }1675 }
1676};1676};
16771677
1678fn testThreadName(thread: *Thread) !void {1678fn testThreadName(io: Io, thread: *Thread) !void {
1679 const testCases = &[_][]const u8{1679 const testCases = &[_][]const u8{
1680 "mythread",1680 "mythread",
1681 "b" ** max_name_len,1681 "b" ** max_name_len,
1682 };1682 };
16831683
1684 inline for (testCases) |tc| {1684 inline for (testCases) |tc| {
1685 try thread.setName(tc);1685 try thread.setName(io, tc);
16861686
1687 var name_buffer: [max_name_len:0]u8 = undefined;1687 var name_buffer: [max_name_len:0]u8 = undefined;
16881688
...@@ -1697,6 +1697,8 @@ fn testThreadName(thread: *Thread) !void {...@@ -1697,6 +1697,8 @@ fn testThreadName(thread: *Thread) !void {
1697test "setName, getName" {1697test "setName, getName" {
1698 if (builtin.single_threaded) return error.SkipZigTest;1698 if (builtin.single_threaded) return error.SkipZigTest;
16991699
1700 const io = testing.io;
1701
1700 const Context = struct {1702 const Context = struct {
1701 start_wait_event: ResetEvent = .unset,1703 start_wait_event: ResetEvent = .unset,
1702 test_done_event: ResetEvent = .unset,1704 test_done_event: ResetEvent = .unset,
...@@ -1710,11 +1712,11 @@ test "setName, getName" {...@@ -1710,11 +1712,11 @@ test "setName, getName" {
1710 ctx.start_wait_event.wait();1712 ctx.start_wait_event.wait();
17111713
1712 switch (native_os) {1714 switch (native_os) {
1713 .windows => testThreadName(&ctx.thread) catch |err| switch (err) {1715 .windows => testThreadName(io, &ctx.thread) catch |err| switch (err) {
1714 error.Unsupported => return error.SkipZigTest,1716 error.Unsupported => return error.SkipZigTest,
1715 else => return err,1717 else => return err,
1716 },1718 },
1717 else => try testThreadName(&ctx.thread),1719 else => try testThreadName(io, &ctx.thread),
1718 }1720 }
17191721
1720 // Signal our test is done1722 // Signal our test is done
...@@ -1734,14 +1736,14 @@ test "setName, getName" {...@@ -1734,14 +1736,14 @@ test "setName, getName" {
17341736
1735 switch (native_os) {1737 switch (native_os) {
1736 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {1738 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
1737 const res = thread.setName("foobar");1739 const res = thread.setName(io, "foobar");
1738 try std.testing.expectError(error.Unsupported, res);1740 try std.testing.expectError(error.Unsupported, res);
1739 },1741 },
1740 .windows => testThreadName(&thread) catch |err| switch (err) {1742 .windows => testThreadName(io, &thread) catch |err| switch (err) {
1741 error.Unsupported => return error.SkipZigTest,1743 error.Unsupported => return error.SkipZigTest,
1742 else => return err,1744 else => return err,
1743 },1745 },
1744 else => try testThreadName(&thread),1746 else => try testThreadName(io, &thread),
1745 }1747 }
17461748
1747 context.thread_done_event.set();1749 context.thread_done_event.set();
lib/std/c.zig+7-5
...@@ -148,9 +148,10 @@ pub const dev_t = switch (native_os) {...@@ -148,9 +148,10 @@ pub const dev_t = switch (native_os) {
148pub const mode_t = switch (native_os) {148pub const mode_t = switch (native_os) {
149 .linux => linux.mode_t,149 .linux => linux.mode_t,
150 .emscripten => emscripten.mode_t,150 .emscripten => emscripten.mode_t,
151 .openbsd, .haiku, .netbsd, .illumos, .wasi, .windows => u32,151 .openbsd, .haiku, .netbsd, .illumos, .windows => u32,
152 // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L44152 // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L44
153 .freebsd, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .dragonfly, .serenity => u16,153 .freebsd, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .dragonfly, .serenity => u16,
154 .wasi => if (builtin.link_libc) u32 else u0, // WASI libc emulates mode.
154 else => u0,155 else => u0,
155};156};
156157
...@@ -160,9 +161,10 @@ pub const nlink_t = switch (native_os) {...@@ -160,9 +161,10 @@ pub const nlink_t = switch (native_os) {
160 .wasi => c_ulonglong,161 .wasi => c_ulonglong,
161 // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L45162 // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L45
162 .freebsd, .serenity => u64,163 .freebsd, .serenity => u64,
163 .openbsd, .netbsd, .illumos => u32,164 .openbsd, .netbsd, .dragonfly, .illumos => u32,
164 .haiku => i32,165 .haiku => i32,
165 else => void,166 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => u16,
167 else => u0,
166};168};
167169
168pub const uid_t = switch (native_os) {170pub const uid_t = switch (native_os) {
...@@ -10608,7 +10610,7 @@ pub extern "c" fn munmap(addr: *align(page_size) const anyopaque, len: usize) c_...@@ -10608,7 +10610,7 @@ pub extern "c" fn munmap(addr: *align(page_size) const anyopaque, len: usize) c_
10608pub extern "c" fn mremap(addr: ?*align(page_size) const anyopaque, old_len: usize, new_len: usize, flags: MREMAP, ...) *anyopaque;10610pub extern "c" fn mremap(addr: ?*align(page_size) const anyopaque, old_len: usize, new_len: usize, flags: MREMAP, ...) *anyopaque;
10609pub extern "c" fn mprotect(addr: *align(page_size) anyopaque, len: usize, prot: c_uint) c_int;10611pub extern "c" fn mprotect(addr: *align(page_size) anyopaque, len: usize, prot: c_uint) c_int;
10610pub extern "c" fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) c_int;10612pub 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;
10612pub extern "c" fn unlink(path: [*:0]const u8) c_int;10614pub extern "c" fn unlink(path: [*:0]const u8) c_int;
10613pub extern "c" fn unlinkat(dirfd: fd_t, path: [*:0]const u8, flags: c_uint) c_int;10615pub extern "c" fn unlinkat(dirfd: fd_t, path: [*:0]const u8, flags: c_uint) c_int;
10614pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8;10616pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8;
...@@ -10740,7 +10742,7 @@ pub extern "c" fn free(?*anyopaque) void;...@@ -10740,7 +10742,7 @@ pub extern "c" fn free(?*anyopaque) void;
10740pub extern "c" fn futimes(fd: fd_t, times: ?*[2]timeval) c_int;10742pub extern "c" fn futimes(fd: fd_t, times: ?*[2]timeval) c_int;
10741pub extern "c" fn utimes(path: [*:0]const u8, times: ?*[2]timeval) c_int;10743pub 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;
10744pub extern "c" fn futimens(fd: fd_t, times: ?*const [2]timespec) c_int;10746pub extern "c" fn futimens(fd: fd_t, times: ?*const [2]timespec) c_int;
1074510747
10746pub extern "c" fn pthread_create(10748pub extern "c" fn pthread_create(
lib/std/c/darwin.zig+1-1
...@@ -349,7 +349,7 @@ pub const VM = struct {...@@ -349,7 +349,7 @@ pub const VM = struct {
349pub const exception_type_t = c_int;349pub const exception_type_t = c_int;
350350
351pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32;351pub 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;
353pub extern "c" fn _dyld_image_count() u32;353pub extern "c" fn _dyld_image_count() u32;
354pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;354pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header;
355pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize;355pub 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 {...@@ -250,7 +250,7 @@ pub const kinfo_file = extern struct {
250 /// Reserved for future cap_rights250 /// Reserved for future cap_rights
251 _cap_spare: u64,251 _cap_spare: u64,
252 /// Path to file, if any.252 /// Path to file, if any.
253 path: [PATH_MAX - 1:0]u8,253 path: [PATH_MAX]u8,
254254
255 comptime {255 comptime {
256 assert(@sizeOf(@This()) == KINFO_FILE_SIZE);256 assert(@sizeOf(@This()) == KINFO_FILE_SIZE);
lib/std/crypto/Certificate/Bundle.zig+16-16
...@@ -9,8 +9,8 @@ const builtin = @import("builtin");...@@ -9,8 +9,8 @@ const builtin = @import("builtin");
99
10const std = @import("../../std.zig");10const std = @import("../../std.zig");
11const Io = std.Io;11const Io = std.Io;
12const Dir = std.Io.Dir;
12const assert = std.debug.assert;13const assert = std.debug.assert;
13const fs = std.fs;
14const mem = std.mem;14const mem = std.mem;
15const crypto = std.crypto;15const crypto = std.crypto;
16const Allocator = std.mem.Allocator;16const Allocator = std.mem.Allocator;
...@@ -171,17 +171,17 @@ fn rescanWindows(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanW...@@ -171,17 +171,17 @@ fn rescanWindows(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanW
171 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);171 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
172}172}
173173
174pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError;174pub const AddCertsFromDirPathError = Io.File.OpenError || AddCertsFromDirError;
175175
176pub fn addCertsFromDirPath(176pub fn addCertsFromDirPath(
177 cb: *Bundle,177 cb: *Bundle,
178 gpa: Allocator,178 gpa: Allocator,
179 io: Io,179 io: Io,
180 dir: fs.Dir,180 dir: Io.Dir,
181 sub_dir_path: []const u8,181 sub_dir_path: []const u8,
182) AddCertsFromDirPathError!void {182) AddCertsFromDirPathError!void {
183 var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });183 var iterable_dir = try dir.openDir(io, sub_dir_path, .{ .iterate = true });
184 defer iterable_dir.close();184 defer iterable_dir.close(io);
185 return addCertsFromDir(cb, gpa, io, iterable_dir);185 return addCertsFromDir(cb, gpa, io, iterable_dir);
186}186}
187187
...@@ -192,27 +192,27 @@ pub fn addCertsFromDirPathAbsolute(...@@ -192,27 +192,27 @@ pub fn addCertsFromDirPathAbsolute(
192 now: Io.Timestamp,192 now: Io.Timestamp,
193 abs_dir_path: []const u8,193 abs_dir_path: []const u8,
194) AddCertsFromDirPathError!void {194) AddCertsFromDirPathError!void {
195 assert(fs.path.isAbsolute(abs_dir_path));195 assert(Dir.path.isAbsolute(abs_dir_path));
196 var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });196 var iterable_dir = try Dir.openDirAbsolute(io, abs_dir_path, .{ .iterate = true });
197 defer iterable_dir.close();197 defer iterable_dir.close(io);
198 return addCertsFromDir(cb, gpa, io, now, iterable_dir);198 return addCertsFromDir(cb, gpa, io, now, iterable_dir);
199}199}
200200
201pub const AddCertsFromDirError = AddCertsFromFilePathError;201pub 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 {
204 var it = iterable_dir.iterate();204 var it = iterable_dir.iterate();
205 while (try it.next()) |entry| {205 while (try it.next(io)) |entry| {
206 switch (entry.kind) {206 switch (entry.kind) {
207 .file, .sym_link => {},207 .file, .sym_link => {},
208 else => continue,208 else => continue,
209 }209 }
210210
211 try addCertsFromFilePath(cb, gpa, io, now, iterable_dir.adaptToNewApi(), entry.name);211 try addCertsFromFilePath(cb, gpa, io, now, iterable_dir, entry.name);
212 }212 }
213}213}
214214
215pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError || Io.Clock.Error;215pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError || Io.Clock.Error;
216216
217pub fn addCertsFromFilePathAbsolute(217pub fn addCertsFromFilePathAbsolute(
218 cb: *Bundle,218 cb: *Bundle,
...@@ -221,8 +221,8 @@ pub fn addCertsFromFilePathAbsolute(...@@ -221,8 +221,8 @@ pub fn addCertsFromFilePathAbsolute(
221 now: Io.Timestamp,221 now: Io.Timestamp,
222 abs_file_path: []const u8,222 abs_file_path: []const u8,
223) AddCertsFromFilePathError!void {223) AddCertsFromFilePathError!void {
224 var file = try fs.openFileAbsolute(abs_file_path, .{});224 var file = try Io.Dir.openFileAbsolute(io, abs_file_path, .{});
225 defer file.close();225 defer file.close(io);
226 var file_reader = file.reader(io, &.{});226 var file_reader = file.reader(io, &.{});
227 return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds());227 return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds());
228}228}
...@@ -242,8 +242,8 @@ pub fn addCertsFromFilePath(...@@ -242,8 +242,8 @@ pub fn addCertsFromFilePath(
242}242}
243243
244pub const AddCertsFromFileError = Allocator.Error ||244pub const AddCertsFromFileError = Allocator.Error ||
245 fs.File.GetSeekPosError ||245 Io.File.Reader.Error ||
246 fs.File.ReadError ||246 Io.File.Reader.SizeError ||
247 ParseCertError ||247 ParseCertError ||
248 std.base64.Error ||248 std.base64.Error ||
249 error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker, Streaming };249 error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker, Streaming };
lib/std/crypto/Certificate/Bundle/macos.zig+2-3
...@@ -6,7 +6,7 @@ const mem = std.mem;...@@ -6,7 +6,7 @@ const mem = std.mem;
6const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
7const Bundle = @import("../Bundle.zig");7const 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
11pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanMacError!void {11pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanMacError!void {
12 cb.bytes.clearRetainingCapacity();12 cb.bytes.clearRetainingCapacity();
...@@ -17,9 +17,8 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanM...@@ -17,9 +17,8 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanM
17 "/Library/Keychains/System.keychain",17 "/Library/Keychains/System.keychain",
18 };18 };
1919
20 _ = io; // TODO migrate file system to use std.Io
21 for (keychain_paths) |keychain_path| {20 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) {
23 error.StreamTooLong => return error.FileTooBig,22 error.StreamTooLong => return error.FileTooBig,
24 else => |e| return e,23 else => |e| return e,
25 };24 };
lib/std/crypto/benchmark.zig+6-4
...@@ -1,10 +1,12 @@...@@ -1,10 +1,12 @@
1// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig1// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
22
3const std = @import("std");
4const builtin = @import("builtin");3const builtin = @import("builtin");
4
5const std = @import("std");
6const Io = std.Io;
5const mem = std.mem;7const mem = std.mem;
6const time = std.time;8const time = std.time;
7const Timer = time.Timer;9const Timer = std.time.Timer;
8const crypto = std.crypto;10const crypto = std.crypto;
911
10const KiB = 1024;12const KiB = 1024;
...@@ -504,7 +506,7 @@ fn mode(comptime x: comptime_int) comptime_int {...@@ -504,7 +506,7 @@ fn mode(comptime x: comptime_int) comptime_int {
504pub fn main() !void {506pub fn main() !void {
505 // Size of buffer is about size of printed message.507 // Size of buffer is about size of printed message.
506 var stdout_buffer: [0x100]u8 = undefined;508 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);
508 const stdout = &stdout_writer.interface;510 const stdout = &stdout_writer.interface;
509511
510 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);512 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
...@@ -554,7 +556,7 @@ pub fn main() !void {...@@ -554,7 +556,7 @@ pub fn main() !void {
554 }556 }
555 }557 }
556558
557 var io_threaded = std.Io.Threaded.init(arena_allocator);559 var io_threaded = std.Io.Threaded.init(arena_allocator, .{});
558 defer io_threaded.deinit();560 defer io_threaded.deinit();
559 const io = io_threaded.io();561 const io = io_threaded.io();
560562
lib/std/crypto/codecs/asn1/test.zig+3-3
...@@ -73,8 +73,8 @@ test AllTypes {...@@ -73,8 +73,8 @@ test AllTypes {
73 try std.testing.expectEqualSlices(u8, encoded, buf);73 try std.testing.expectEqualSlices(u8, encoded, buf);
7474
75 // Use this to update test file.75 // Use this to update test file.
76 // const dir = try std.fs.cwd().openDir("lib/std/crypto/asn1", .{});76 // const dir = try Io.Dir.cwd().openDir(io, "lib/std/crypto/asn1", .{});
77 // var file = try dir.createFile(path, .{});77 // var file = try dir.createFile(io, path, .{});
78 // defer file.close();78 // defer file.close(io);
79 // try file.writeAll(buf);79 // try file.writeAll(buf);
80}80}
lib/std/crypto/tls.zig-1
...@@ -32,7 +32,6 @@...@@ -32,7 +32,6 @@
3232
33const std = @import("../std.zig");33const std = @import("../std.zig");
34const Tls = @This();34const Tls = @This();
35const net = std.net;
36const mem = std.mem;35const mem = std.mem;
37const crypto = std.crypto;36const crypto = std.crypto;
38const assert = std.debug.assert;37const assert = std.debug.assert;
lib/std/debug.zig+250-278
...@@ -1,14 +1,13 @@...@@ -1,14 +1,13 @@
1const std = @import("std.zig");1const std = @import("std.zig");
2const Io = std.Io;2const Io = std.Io;
3const Writer = std.Io.Writer;3const Writer = std.Io.Writer;
4const tty = std.Io.tty;
5const math = std.math;4const math = std.math;
6const mem = std.mem;5const mem = std.mem;
7const posix = std.posix;6const posix = std.posix;
8const fs = std.fs;7const fs = std.fs;
9const testing = std.testing;8const testing = std.testing;
10const Allocator = mem.Allocator;9const Allocator = mem.Allocator;
11const File = std.fs.File;10const File = std.Io.File;
12const windows = std.os.windows;11const windows = std.os.windows;
1312
14const builtin = @import("builtin");13const builtin = @import("builtin");
...@@ -60,7 +59,7 @@ pub const cpu_context = @import("debug/cpu_context.zig");...@@ -60,7 +59,7 @@ pub const cpu_context = @import("debug/cpu_context.zig");
60/// };59/// };
61/// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's60/// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's
62/// /// return address, or 0 if the end of the stack has been reached.61/// /// 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;
64/// ```63/// ```
65pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo"))64pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo"))
66 root.debug.SelfInfo65 root.debug.SelfInfo
...@@ -262,53 +261,54 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {...@@ -262,53 +261,54 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
262 else => true,261 else => true,
263};262};
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.
266///265///
267/// During the lock, any `std.Progress` information is cleared from the terminal.266/// 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.
277///267///
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.
279///272///
280/// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple273/// The returned `Writer` does not need to be manually flushed: flushing is
281/// times. The primary motivation is that this allows the panic handler to safely dump the stack274/// performed automatically when the matching `unlockStderr` call occurs.
282/// trace and panic message even if the mutex was held at the panic site.
283///275///
284/// The returned `Writer` does not need to be manually flushed: flushing is performed automatically276/// This is a low-level debugging primitive that bypasses the `Io` interface,
285/// when the matching `unlockStderrWriter` call occurs.277/// writing directly to stderr using the most basic syscalls available. This
286pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } {278/// function does not switch threads, switch stacks, or suspend.
287 const global = struct {279///
288 var conf: ?tty.Config = null;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.
289 };288 };
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.? };
296}289}
297290
298pub fn unlockStderrWriter() void {291pub fn unlockStderr() void {
299 std.Progress.unlockStderrWriter();292 const io = std.Options.debug_io;
293 io.unlockStderr();
300}294}
301295
302/// Print to stderr, silently returning on failure. Intended for use in "printf296/// Writes to stderr, ignoring errors.
303/// debugging". Use `std.log` functions for proper logging.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.
304///301///
305/// Uses a 64-byte buffer for formatted printing which is flushed before this302/// Uses a 64-byte buffer for formatted printing which is flushed before this
306/// function returns.303/// function returns.
304///
305/// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to
306/// integrate with the application's chosen `Io` implementation.
307pub fn print(comptime fmt: []const u8, args: anytype) void {307pub fn print(comptime fmt: []const u8, args: anytype) void {
308 var buffer: [64]u8 = undefined;308 var buffer: [64]u8 = undefined;
309 const bw, _ = lockStderrWriter(&buffer);309 const stderr = lockStderr(&buffer);
310 defer unlockStderrWriter();310 defer unlockStderr();
311 nosuspend bw.print(fmt, args) catch return;311 stderr.file_writer.interface.print(fmt, args) catch return;
312}312}
313313
314/// Marked `inline` to propagate a comptime-known error to callers.314/// Marked `inline` to propagate a comptime-known error to callers.
...@@ -323,43 +323,44 @@ pub inline fn getSelfDebugInfo() !*SelfInfo {...@@ -323,43 +323,44 @@ pub inline fn getSelfDebugInfo() !*SelfInfo {
323/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.323/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
324/// Obtains the stderr mutex while dumping.324/// Obtains the stderr mutex while dumping.
325pub fn dumpHex(bytes: []const u8) void {325pub fn dumpHex(bytes: []const u8) void {
326 const bw, const ttyconf = lockStderrWriter(&.{});326 const stderr = lockStderr(&.{}).terminal();
327 defer unlockStderrWriter();327 defer unlockStderr();
328 dumpHexFallible(bw, ttyconf, bytes) catch {};328 dumpHexFallible(stderr, bytes) catch {};
329}329}
330330
331/// Prints a hexadecimal view of the bytes, returning any error that occurs.331/// 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;
333 var chunks = mem.window(u8, bytes, 16, 16);334 var chunks = mem.window(u8, bytes, 16, 16);
334 while (chunks.next()) |window| {335 while (chunks.next()) |window| {
335 // 1. Print the address.336 // 1. Print the address.
336 const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10;337 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);
338 // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.339 // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.
339 // Also, make sure all lines are aligned by padding the address.340 // Also, make sure all lines are aligned by padding the address.
340 try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });341 try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
341 try tty_config.setColor(bw, .reset);342 try t.setColor(.reset);
342343
343 // 2. Print the bytes.344 // 2. Print the bytes.
344 for (window, 0..) |byte, index| {345 for (window, 0..) |byte, index| {
345 try bw.print("{X:0>2} ", .{byte});346 try w.print("{X:0>2} ", .{byte});
346 if (index == 7) try bw.writeByte(' ');347 if (index == 7) try w.writeByte(' ');
347 }348 }
348 try bw.writeByte(' ');349 try w.writeByte(' ');
349 if (window.len < 16) {350 if (window.len < 16) {
350 var missing_columns = (16 - window.len) * 3;351 var missing_columns = (16 - window.len) * 3;
351 if (window.len < 8) missing_columns += 1;352 if (window.len < 8) missing_columns += 1;
352 try bw.splatByteAll(' ', missing_columns);353 try w.splatByteAll(' ', missing_columns);
353 }354 }
354355
355 // 3. Print the characters.356 // 3. Print the characters.
356 for (window) |byte| {357 for (window) |byte| {
357 if (std.ascii.isPrint(byte)) {358 if (std.ascii.isPrint(byte)) {
358 try bw.writeByte(byte);359 try w.writeByte(byte);
359 } else {360 } else {
360 // Related: https://github.com/ziglang/zig/issues/7600361 // Related: https://github.com/ziglang/zig/issues/7600
361 if (tty_config == .windows_api) {362 if (t.mode == .windows_api) {
362 try bw.writeByte('.');363 try w.writeByte('.');
363 continue;364 continue;
364 }365 }
365366
...@@ -367,24 +368,25 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !...@@ -367,24 +368,25 @@ pub fn dumpHexFallible(bw: *Writer, tty_config: tty.Config, bytes: []const u8) !
367 // We don't want to do this for all control codes because most control codes apart from368 // We don't want to do this for all control codes because most control codes apart from
368 // the ones that Zig has escape sequences for are likely not very useful to print as symbols.369 // the ones that Zig has escape sequences for are likely not very useful to print as symbols.
369 switch (byte) {370 switch (byte) {
370 '\n' => try bw.writeAll("␊"),371 '\n' => try w.writeAll("␊"),
371 '\r' => try bw.writeAll("␍"),372 '\r' => try w.writeAll("␍"),
372 '\t' => try bw.writeAll("␉"),373 '\t' => try w.writeAll("␉"),
373 else => try bw.writeByte('.'),374 else => try w.writeByte('.'),
374 }375 }
375 }376 }
376 }377 }
377 try bw.writeByte('\n');378 try w.writeByte('\n');
378 }379 }
379}380}
380381
381test dumpHexFallible {382test dumpHexFallible {
383 const gpa = testing.allocator;
382 const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 };384 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);
384 defer aw.deinit();386 defer aw.deinit();
385387
386 try dumpHexFallible(&aw.writer, .no_color, bytes);388 try dumpHexFallible(.{ .writer = &aw.writer, .mode = .no_color }, bytes);
387 const expected = try std.fmt.allocPrint(std.testing.allocator,389 const expected = try std.fmt.allocPrint(gpa,
388 \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........390 \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........
389 \\{x:0>[2]} 01 12 13 ...391 \\{x:0>[2]} 01 12 13 ...
390 \\392 \\
...@@ -393,8 +395,8 @@ test dumpHexFallible {...@@ -393,8 +395,8 @@ test dumpHexFallible {
393 @intFromPtr(bytes.ptr) + 16,395 @intFromPtr(bytes.ptr) + 16,
394 @sizeOf(usize) * 2,396 @sizeOf(usize) * 2,
395 });397 });
396 defer std.testing.allocator.free(expected);398 defer gpa.free(expected);
397 try std.testing.expectEqualStrings(expected, aw.written());399 try testing.expectEqualStrings(expected, aw.written());
398}400}
399401
400/// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic.402/// 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...@@ -409,7 +411,7 @@ pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *con
409/// away, and in fact the optimizer is able to use the assertion in its411/// away, and in fact the optimizer is able to use the assertion in its
410/// heuristics.412/// heuristics.
411///413///
412/// Inside a test block, it is best to use the `std.testing` module rather than414/// Inside a test block, it is best to use the `testing` module rather than
413/// this function, because this function may not detect a test failure in415/// this function, because this function may not detect a test failure in
414/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert416/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert
415/// function is the correct function to use.417/// function is the correct function to use.
...@@ -483,10 +485,7 @@ const use_trap_panic = switch (builtin.zig_backend) {...@@ -483,10 +485,7 @@ const use_trap_panic = switch (builtin.zig_backend) {
483};485};
484486
485/// Dumps a stack trace to standard error, then aborts.487/// Dumps a stack trace to standard error, then aborts.
486pub fn defaultPanic(488pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
487 msg: []const u8,
488 first_trace_addr: ?usize,
489) noreturn {
490 @branchHint(.cold);489 @branchHint(.cold);
491490
492 if (use_trap_panic) @trap();491 if (use_trap_panic) @trap();
...@@ -522,7 +521,7 @@ pub fn defaultPanic(...@@ -522,7 +521,7 @@ pub fn defaultPanic(
522 }521 }
523 @trap();522 @trap();
524 },523 },
525 .cuda, .amdhsa => std.posix.abort(),524 .cuda, .amdhsa => std.process.abort(),
526 .plan9 => {525 .plan9 => {
527 var status: [std.os.plan9.ERRMAX]u8 = undefined;526 var status: [std.os.plan9.ERRMAX]u8 = undefined;
528 const len = @min(msg.len, status.len - 1);527 const len = @min(msg.len, status.len - 1);
...@@ -546,26 +545,27 @@ pub fn defaultPanic(...@@ -546,26 +545,27 @@ pub fn defaultPanic(
546 _ = panicking.fetchAdd(1, .seq_cst);545 _ = panicking.fetchAdd(1, .seq_cst);
547546
548 trace: {547 trace: {
549 const stderr, const tty_config = lockStderrWriter(&.{});548 const stderr = lockStderr(&.{}).terminal();
550 defer unlockStderrWriter();549 defer unlockStderr();
550 const writer = stderr.writer;
551551
552 if (builtin.single_threaded) {552 if (builtin.single_threaded) {
553 stderr.print("panic: ", .{}) catch break :trace;553 writer.print("panic: ", .{}) catch break :trace;
554 } else {554 } else {
555 const current_thread_id = std.Thread.getCurrentId();555 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;
557 }557 }
558 stderr.print("{s}\n", .{msg}) catch break :trace;558 writer.print("{s}\n", .{msg}) catch break :trace;
559559
560 if (@errorReturnTrace()) |t| if (t.index > 0) {560 if (@errorReturnTrace()) |t| if (t.index > 0) {
561 stderr.writeAll("error return context:\n") catch break :trace;561 writer.writeAll("error return context:\n") catch break :trace;
562 writeStackTrace(t, stderr, tty_config) catch break :trace;562 writeStackTrace(t, stderr) catch break :trace;
563 stderr.writeAll("\nstack trace:\n") catch break :trace;563 writer.writeAll("\nstack trace:\n") catch break :trace;
564 };564 };
565 writeCurrentStackTrace(.{565 writeCurrentStackTrace(.{
566 .first_address = first_trace_addr orelse @returnAddress(),566 .first_address = first_trace_addr orelse @returnAddress(),
567 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!567 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
568 }, stderr, tty_config) catch break :trace;568 }, stderr) catch break :trace;
569 }569 }
570570
571 waitForOtherThreadToFinishPanicking();571 waitForOtherThreadToFinishPanicking();
...@@ -575,12 +575,13 @@ pub fn defaultPanic(...@@ -575,12 +575,13 @@ pub fn defaultPanic(
575 // A panic happened while trying to print a previous panic message.575 // A panic happened while trying to print a previous panic message.
576 // We're still holding the mutex but that's fine as we're going to576 // We're still holding the mutex but that's fine as we're going to
577 // call abort().577 // 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 {};
579 },580 },
580 else => {}, // Panicked while printing the recursive panic message.581 else => {}, // Panicked while printing the recursive panic message.
581 }582 }
582583
583 posix.abort();584 std.process.abort();
584}585}
585586
586/// Must be called only after adding 1 to `panicking`. There are three callsites.587/// 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:...@@ -621,13 +622,16 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
621 var it: StackIterator = .init(options.context);622 var it: StackIterator = .init(options.context);
622 defer it.deinit();623 defer it.deinit();
623 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;624 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
625
626 const io = std.Options.debug_io;
627
624 var total_frames: usize = 0;628 var total_frames: usize = 0;
625 var index: usize = 0;629 var index: usize = 0;
626 var wait_for = options.first_address;630 var wait_for = options.first_address;
627 // Ideally, we would iterate the whole stack so that the `index` in the returned trace was631 // Ideally, we would iterate the whole stack so that the `index` in the returned trace was
628 // indicative of how many frames were skipped. However, this has a significant runtime cost632 // indicative of how many frames were skipped. However, this has a significant runtime cost
629 // in some cases, so at least for now, we don't do that.633 // 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)) {
631 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,635 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,
632 .end => break,636 .end => break,
633 .frame => |ret_addr| {637 .frame => |ret_addr| {
...@@ -653,37 +657,36 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:...@@ -653,37 +657,36 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
653/// Write the current stack trace to `writer`, annotated with source locations.657/// Write the current stack trace to `writer`, annotated with source locations.
654///658///
655/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.659/// 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 {660pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void {
657 var threaded: Io.Threaded = .init_single_threaded;661 const writer = t.writer;
658 const io = threaded.ioBasic();
659
660 if (!std.options.allow_stack_tracing) {662 if (!std.options.allow_stack_tracing) {
661 tty_config.setColor(writer, .dim) catch {};663 t.setColor(.dim) catch {};
662 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});664 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
663 tty_config.setColor(writer, .reset) catch {};665 t.setColor(.reset) catch {};
664 return;666 return;
665 }667 }
666 const di_gpa = getDebugInfoAllocator();668 const di_gpa = getDebugInfoAllocator();
667 const di = getSelfDebugInfo() catch |err| switch (err) {669 const di = getSelfDebugInfo() catch |err| switch (err) {
668 error.UnsupportedTarget => {670 error.UnsupportedTarget => {
669 tty_config.setColor(writer, .dim) catch {};671 t.setColor(.dim) catch {};
670 try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});672 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 {};
672 return;674 return;
673 },675 },
674 };676 };
675 var it: StackIterator = .init(options.context);677 var it: StackIterator = .init(options.context);
676 defer it.deinit();678 defer it.deinit();
677 if (!it.stratOk(options.allow_unsafe_unwind)) {679 if (!it.stratOk(options.allow_unsafe_unwind)) {
678 tty_config.setColor(writer, .dim) catch {};680 t.setColor(.dim) catch {};
679 try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});681 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 {};
681 return;683 return;
682 }684 }
683 var total_frames: usize = 0;685 var total_frames: usize = 0;
684 var wait_for = options.first_address;686 var wait_for = options.first_address;
685 var printed_any_frame = false;687 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)) {
687 .switch_to_fp => |unwind_error| {690 .switch_to_fp => |unwind_error| {
688 switch (StackIterator.fp_usability) {691 switch (StackIterator.fp_usability) {
689 .useless, .unsafe => {},692 .useless, .unsafe => {},
...@@ -700,31 +703,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -700,31 +703,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
700 error.Unexpected => "unexpected error",703 error.Unexpected => "unexpected error",
701 };704 };
702 if (it.stratOk(options.allow_unsafe_unwind)) {705 if (it.stratOk(options.allow_unsafe_unwind)) {
703 tty_config.setColor(writer, .dim) catch {};706 t.setColor(.dim) catch {};
704 try writer.print(707 try writer.print(
705 "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",708 "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",
706 .{ module_name, unwind_error.address, caption },709 .{ module_name, unwind_error.address, caption },
707 );710 );
708 tty_config.setColor(writer, .reset) catch {};711 t.setColor(.reset) catch {};
709 } else {712 } else {
710 tty_config.setColor(writer, .dim) catch {};713 t.setColor(.dim) catch {};
711 try writer.print(714 try writer.print(
712 "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",715 "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",
713 .{ module_name, unwind_error.address, caption },716 .{ module_name, unwind_error.address, caption },
714 );717 );
715 tty_config.setColor(writer, .reset) catch {};718 t.setColor(.reset) catch {};
716 return;719 return;
717 }720 }
718 },721 },
719 .end => break,722 .end => break,
720 .frame => |ret_addr| {723 .frame => |ret_addr| {
721 if (total_frames > 10_000) {724 if (total_frames > 10_000) {
722 tty_config.setColor(writer, .dim) catch {};725 t.setColor(.dim) catch {};
723 try writer.print(726 try writer.print(
724 "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",727 "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",
725 .{total_frames},728 .{total_frames},
726 );729 );
727 tty_config.setColor(writer, .reset) catch {};730 t.setColor(.reset) catch {};
728 return;731 return;
729 }732 }
730 total_frames += 1;733 total_frames += 1;
...@@ -734,7 +737,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -734,7 +737,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
734 }737 }
735 // `ret_addr` is the return address, which is *after* the function call.738 // `ret_addr` is the return address, which is *after* the function call.
736 // Subtract 1 to get an address *in* the function call for a better source location.739 // 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);
738 printed_any_frame = true;741 printed_any_frame = true;
739 },742 },
740 };743 };
...@@ -742,8 +745,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -742,8 +745,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
742}745}
743/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.746/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.
744pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {747pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
745 const stderr, const tty_config = lockStderrWriter(&.{});748 const stderr = lockStderr(&.{}).terminal();
746 defer unlockStderrWriter();749 defer unlockStderr();
747 writeCurrentStackTrace(.{750 writeCurrentStackTrace(.{
748 .first_address = a: {751 .first_address = a: {
749 if (options.first_address) |a| break :a a;752 if (options.first_address) |a| break :a a;
...@@ -752,33 +755,30 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {...@@ -752,33 +755,30 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
752 },755 },
753 .context = options.context,756 .context = options.context,
754 .allow_unsafe_unwind = options.allow_unsafe_unwind,757 .allow_unsafe_unwind = options.allow_unsafe_unwind,
755 }, stderr, tty_config) catch |err| switch (err) {758 }, stderr) catch |err| switch (err) {
756 error.WriteFailed => {},759 error.WriteFailed => {},
757 };760 };
758}761}
759762
760pub const FormatStackTrace = struct {763pub const FormatStackTrace = struct {
761 stack_trace: StackTrace,764 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 {767 pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void {
765 try writer.writeAll("\n");768 try writer.writeByte('\n');
766 try writeStackTrace(&context.stack_trace, writer, context.tty_config);769 try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_mode });
767 }770 }
768};771};
769772
770/// Write a previously captured stack trace to `writer`, annotated with source locations.773/// 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;
772 if (!std.options.allow_stack_tracing) {776 if (!std.options.allow_stack_tracing) {
773 tty_config.setColor(writer, .dim) catch {};777 t.setColor(.dim) catch {};
774 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});778 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
775 tty_config.setColor(writer, .reset) catch {};779 t.setColor(.reset) catch {};
776 return;780 return;
777 }781 }
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
783 // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if783 // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if
784 // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.784 // `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...@@ -787,29 +787,30 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.C
787 const di_gpa = getDebugInfoAllocator();787 const di_gpa = getDebugInfoAllocator();
788 const di = getSelfDebugInfo() catch |err| switch (err) {788 const di = getSelfDebugInfo() catch |err| switch (err) {
789 error.UnsupportedTarget => {789 error.UnsupportedTarget => {
790 tty_config.setColor(writer, .dim) catch {};790 t.setColor(.dim) catch {};
791 try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});791 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 {};
793 return;793 return;
794 },794 },
795 };795 };
796 const io = std.Options.debug_io;
796 const captured_frames = @min(n_frames, st.instruction_addresses.len);797 const captured_frames = @min(n_frames, st.instruction_addresses.len);
797 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {798 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
798 // `ret_addr` is the return address, which is *after* the function call.799 // `ret_addr` is the return address, which is *after* the function call.
799 // Subtract 1 to get an address *in* the function call for a better source location.800 // 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);
801 }802 }
802 if (n_frames > captured_frames) {803 if (n_frames > captured_frames) {
803 tty_config.setColor(writer, .bold) catch {};804 t.setColor(.bold) catch {};
804 try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});805 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 {};
806 }807 }
807}808}
808/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.809/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
809pub fn dumpStackTrace(st: *const StackTrace) void {810pub fn dumpStackTrace(st: *const StackTrace) void {
810 const stderr, const tty_config = lockStderrWriter(&.{});811 const stderr = lockStderr(&.{}).terminal();
811 defer unlockStderrWriter();812 defer unlockStderr();
812 writeStackTrace(st, stderr, tty_config) catch |err| switch (err) {813 writeStackTrace(st, stderr) catch |err| switch (err) {
813 error.WriteFailed => {},814 error.WriteFailed => {},
814 };815 };
815}816}
...@@ -960,7 +961,7 @@ const StackIterator = union(enum) {...@@ -960,7 +961,7 @@ const StackIterator = union(enum) {
960 },961 },
961 };962 };
962963
963 fn next(it: *StackIterator) Result {964 fn next(it: *StackIterator, io: Io) Result {
964 switch (it.*) {965 switch (it.*) {
965 .ctx_first => |context_ptr| {966 .ctx_first => |context_ptr| {
966 // After the first frame, start actually unwinding.967 // After the first frame, start actually unwinding.
...@@ -976,7 +977,7 @@ const StackIterator = union(enum) {...@@ -976,7 +977,7 @@ const StackIterator = union(enum) {
976 .di => |*unwind_context| {977 .di => |*unwind_context| {
977 const di = getSelfDebugInfo() catch unreachable;978 const di = getSelfDebugInfo() catch unreachable;
978 const di_gpa = getDebugInfoAllocator();979 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| {
980 const pc = unwind_context.pc;981 const pc = unwind_context.pc;
981 const fp = unwind_context.getFp();982 const fp = unwind_context.getFp();
982 it.* = .{ .fp = fp };983 it.* = .{ .fp = fp };
...@@ -1104,170 +1105,146 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {...@@ -1104,170 +1105,146 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
1104 return ptr;1105 return ptr;
1105}1106}
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 {
1108 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {1115 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
1109 error.MissingDebugInfo,1116 error.MissingDebugInfo,
1110 error.UnsupportedDebugInfo,1117 error.UnsupportedDebugInfo,
1111 error.InvalidDebugInfo,1118 error.InvalidDebugInfo,
1112 => .unknown,1119 => .unknown,
1113 error.ReadFailed, error.Unexpected, error.Canceled => s: {1120 error.ReadFailed, error.Unexpected, error.Canceled => s: {
1114 tty_config.setColor(writer, .dim) catch {};1121 t.setColor(.dim) catch {};
1115 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});1122 try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1116 tty_config.setColor(writer, .reset) catch {};1123 t.setColor(.reset) catch {};
1117 break :s .unknown;1124 break :s .unknown;
1118 },1125 },
1119 error.OutOfMemory => s: {1126 error.OutOfMemory => s: {
1120 tty_config.setColor(writer, .dim) catch {};1127 t.setColor(.dim) catch {};
1121 try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});1128 try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1122 tty_config.setColor(writer, .reset) catch {};1129 t.setColor(.reset) catch {};
1123 break :s .unknown;1130 break :s .unknown;
1124 },1131 },
1125 };1132 };
1126 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);1133 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
1127 return printLineInfo(1134 return printLineInfo(
1128 writer,1135 io,
1136 t,
1129 symbol.source_location,1137 symbol.source_location,
1130 address,1138 address,
1131 symbol.name orelse "???",1139 symbol.name orelse "???",
1132 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",1140 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
1133 tty_config,
1134 );1141 );
1135}1142}
1136fn printLineInfo(1143fn printLineInfo(
1137 writer: *Writer,1144 io: Io,
1145 t: Io.Terminal,
1138 source_location: ?SourceLocation,1146 source_location: ?SourceLocation,
1139 address: usize,1147 address: usize,
1140 symbol_name: []const u8,1148 symbol_name: []const u8,
1141 compile_unit_name: []const u8,1149 compile_unit_name: []const u8,
1142 tty_config: tty.Config,
1143) Writer.Error!void {1150) Writer.Error!void {
1144 nosuspend {1151 const writer = t.writer;
1145 tty_config.setColor(writer, .bold) catch {};1152 t.setColor(.bold) catch {};
11461153
1147 if (source_location) |*sl| {1154 if (source_location) |*sl| {
1148 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });1155 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
1149 } else {1156 } else {
1150 try writer.writeAll("???:?:?");1157 try writer.writeAll("???:?:?");
1151 }1158 }
11521159
1153 tty_config.setColor(writer, .reset) catch {};1160 t.setColor(.reset) catch {};
1154 try writer.writeAll(": ");1161 try writer.writeAll(": ");
1155 tty_config.setColor(writer, .dim) catch {};1162 t.setColor(.dim) catch {};
1156 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });1163 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
1157 tty_config.setColor(writer, .reset) catch {};1164 t.setColor(.reset) catch {};
1158 try writer.writeAll("\n");1165 try writer.writeAll("\n");
11591166
1160 // Show the matching source code line if possible1167 // Show the matching source code line if possible
1161 if (source_location) |sl| {1168 if (source_location) |sl| {
1162 if (printLineFromFile(writer, sl)) {1169 if (printLineFromFile(io, writer, sl)) {
1163 if (sl.column > 0) {1170 if (sl.column > 0) {
1164 // The caret already takes one char1171 // The caret already takes one char
1165 const space_needed = @as(usize, @intCast(sl.column - 1));1172 const space_needed = @as(usize, @intCast(sl.column - 1));
11661173
1167 try writer.splatByteAll(' ', space_needed);1174 try writer.splatByteAll(' ', space_needed);
1168 tty_config.setColor(writer, .green) catch {};1175 t.setColor(.green) catch {};
1169 try writer.writeAll("^");1176 try writer.writeAll("^");
1170 tty_config.setColor(writer, .reset) catch {};1177 t.setColor(.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.
1176 }1178 }
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.
1177 }1183 }
1178 }1184 }
1179}1185}
1180fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {1186fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !void {
1181 // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`.1187 // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`.
1182 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) {1188 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);
1184 }1190 }
11851191
1186 // Need this to always block even in async I/O mode, because this could potentially1192 // Need this to always block even in async I/O mode, because this could potentially
1187 // be called from e.g. the event loop code crashing.1193 // be called from e.g. the event loop code crashing.
1188 var f = try fs.cwd().openFile(source_location.file_name, .{});1194 const cwd: Io.Dir = .cwd();
1189 defer f.close();1195 var file = try cwd.openFile(io, source_location.file_name, .{});
1190 // TODO fstat and make sure that the file has the correct size1196 defer file.close(io);
11911197
1192 var buf: [4096]u8 = undefined;1198 var buffer: [4096]u8 = undefined;
1193 var amt_read = try f.read(buf[0..]);1199 var file_reader: File.Reader = .init(file, io, &buffer);
1194 const line_start = seek: {1200 var line_index: usize = 0;
1195 var current_line_start: usize = 0;1201 const r = &file_reader.interface;
1196 var next_line: usize = 1;1202 while (true) {
1197 while (next_line != source_location.line) {1203 line_index += 1;
1198 const slice = buf[current_line_start..amt_read];1204 if (line_index == source_location.line) {
1199 if (mem.findScalar(u8, slice, '\n')) |pos| {1205 // TODO delete hard tabs from the language
1200 next_line += 1;1206 _ = try r.streamDelimiterEnding(writer, '\n');
1201 if (pos == slice.len - 1) {1207 try writer.writeByte('\n');
1202 amt_read = try f.read(buf[0..]);1208 return;
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 }
1233 }1209 }
1234 // Make sure printing last line of file inserts extra newline1210 _ = try r.discardDelimiterInclusive('\n');
1235 try writer.writeByte('\n');
1236 }1211 }
1237}1212}
12381213
1239test printLineFromFile {1214test 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);
1241 defer aw.deinit();1219 defer aw.deinit();
1242 const output_stream = &aw.writer;1220 const output_stream = &aw.writer;
12431221
1244 const allocator = std.testing.allocator;
1245 const join = std.fs.path.join;1222 const join = std.fs.path.join;
1246 const expectError = std.testing.expectError;1223 const expectError = testing.expectError;
1247 const expectEqualStrings = std.testing.expectEqualStrings;1224 const expectEqualStrings = testing.expectEqualStrings;
12481225
1249 var test_dir = std.testing.tmpDir(.{});1226 var test_dir = testing.tmpDir(.{});
1250 defer test_dir.cleanup();1227 defer test_dir.cleanup();
1251 // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.1228 // 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..] });1229 const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
1253 defer allocator.free(test_dir_path);1230 defer gpa.free(test_dir_path);
12541231
1255 // Cases1232 // Cases
1256 {1233 {
1257 const path = try join(allocator, &.{ test_dir_path, "one_line.zig" });1234 const path = try join(gpa, &.{ test_dir_path, "one_line.zig" });
1258 defer allocator.free(path);1235 defer gpa.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" });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 });
1264 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());1241 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
1265 aw.clearRetainingCapacity();1242 aw.clearRetainingCapacity();
1266 }1243 }
1267 {1244 {
1268 const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" });1245 const path = try fs.path.join(gpa, &.{ test_dir_path, "three_lines.zig" });
1269 defer allocator.free(path);1246 defer gpa.free(path);
1270 try test_dir.dir.writeFile(.{1247 try test_dir.dir.writeFile(io, .{
1271 .sub_path = "three_lines.zig",1248 .sub_path = "three_lines.zig",
1272 .data =1249 .data =
1273 \\11250 \\1
...@@ -1276,90 +1253,90 @@ test printLineFromFile {...@@ -1276,90 +1253,90 @@ test printLineFromFile {
1276 ,1253 ,
1277 });1254 });
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 });
1280 try expectEqualStrings("1\n", aw.written());1257 try expectEqualStrings("1\n", aw.written());
1281 aw.clearRetainingCapacity();1258 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 });
1284 try expectEqualStrings("3\n", aw.written());1261 try expectEqualStrings("3\n", aw.written());
1285 aw.clearRetainingCapacity();1262 aw.clearRetainingCapacity();
1286 }1263 }
1287 {1264 {
1288 const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{});1265 const file = try test_dir.dir.createFile(io, "line_overlaps_page_boundary.zig", .{});
1289 defer file.close();1266 defer file.close(io);
1290 const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });1267 const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
1291 defer allocator.free(path);1268 defer gpa.free(path);
12921269
1293 const overlap = 10;1270 const overlap = 10;
1294 var buf: [16]u8 = undefined;1271 var buf: [16]u8 = undefined;
1295 var file_writer = file.writer(&buf);1272 var file_writer = file.writer(io, &buf);
1296 const writer = &file_writer.interface;1273 const writer = &file_writer.interface;
1297 try writer.splatByteAll('a', std.heap.page_size_min - overlap);1274 try writer.splatByteAll('a', std.heap.page_size_min - overlap);
1298 try writer.writeByte('\n');1275 try writer.writeByte('\n');
1299 try writer.splatByteAll('a', overlap);1276 try writer.splatByteAll('a', overlap);
1300 try writer.flush();1277 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 });
1303 try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());1280 try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
1304 aw.clearRetainingCapacity();1281 aw.clearRetainingCapacity();
1305 }1282 }
1306 {1283 {
1307 const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{});1284 const file = try test_dir.dir.createFile(io, "file_ends_on_page_boundary.zig", .{});
1308 defer file.close();1285 defer file.close(io);
1309 const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });1286 const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
1310 defer allocator.free(path);1287 defer gpa.free(path);
13111288
1312 var file_writer = file.writer(&.{});1289 var file_writer = file.writer(io, &.{});
1313 const writer = &file_writer.interface;1290 const writer = &file_writer.interface;
1314 try writer.splatByteAll('a', std.heap.page_size_max);1291 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 });
1317 try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());1294 try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
1318 aw.clearRetainingCapacity();1295 aw.clearRetainingCapacity();
1319 }1296 }
1320 {1297 {
1321 const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{});1298 const file = try test_dir.dir.createFile(io, "very_long_first_line_spanning_multiple_pages.zig", .{});
1322 defer file.close();1299 defer file.close(io);
1323 const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });1300 const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
1324 defer allocator.free(path);1301 defer gpa.free(path);
13251302
1326 var file_writer = file.writer(&.{});1303 var file_writer = file.writer(io, &.{});
1327 const writer = &file_writer.interface;1304 const writer = &file_writer.interface;
1328 try writer.splatByteAll('a', 3 * std.heap.page_size_max);1305 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 });
1333 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());1310 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
1334 aw.clearRetainingCapacity();1311 aw.clearRetainingCapacity();
13351312
1336 try writer.writeAll("a\na");1313 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 });
1339 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());1316 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
1340 aw.clearRetainingCapacity();1317 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 });
1343 try expectEqualStrings("a\n", aw.written());1320 try expectEqualStrings("a\n", aw.written());
1344 aw.clearRetainingCapacity();1321 aw.clearRetainingCapacity();
1345 }1322 }
1346 {1323 {
1347 const file = try test_dir.dir.createFile("file_of_newlines.zig", .{});1324 const file = try test_dir.dir.createFile(io, "file_of_newlines.zig", .{});
1348 defer file.close();1325 defer file.close(io);
1349 const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" });1326 const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" });
1350 defer allocator.free(path);1327 defer gpa.free(path);
13511328
1352 var file_writer = file.writer(&.{});1329 var file_writer = file.writer(io, &.{});
1353 const writer = &file_writer.interface;1330 const writer = &file_writer.interface;
1354 const real_file_start = 3 * std.heap.page_size_min;1331 const real_file_start = 3 * std.heap.page_size_min;
1355 try writer.splatByteAll('\n', real_file_start);1332 try writer.splatByteAll('\n', real_file_start);
1356 try writer.writeAll("abc\ndef");1333 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 });
1359 try expectEqualStrings("abc\n", aw.written());1336 try expectEqualStrings("abc\n", aw.written());
1360 aw.clearRetainingCapacity();1337 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 });
1363 try expectEqualStrings("def\n", aw.written());1340 try expectEqualStrings("def\n", aw.written());
1364 aw.clearRetainingCapacity();1341 aw.clearRetainingCapacity();
1365 }1342 }
...@@ -1563,19 +1540,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex...@@ -1563,19 +1540,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
1563 _ = panicking.fetchAdd(1, .seq_cst);1540 _ = panicking.fetchAdd(1, .seq_cst);
15641541
1565 trace: {1542 trace: {
1566 const stderr, const tty_config = lockStderrWriter(&.{});1543 const stderr = lockStderr(&.{}).terminal();
1567 defer unlockStderrWriter();1544 defer unlockStderr();
15681545
1569 if (addr) |a| {1546 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;
1571 } else {1548 } 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;
1573 }1550 }
1574 if (opt_ctx) |context| {1551 if (opt_ctx) |context| {
1575 writeCurrentStackTrace(.{1552 writeCurrentStackTrace(.{
1576 .context = context,1553 .context = context,
1577 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!1554 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
1578 }, stderr, tty_config) catch break :trace;1555 }, stderr) catch break :trace;
1579 }1556 }
1580 }1557 }
1581 },1558 },
...@@ -1584,7 +1561,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex...@@ -1584,7 +1561,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
1584 // A segfault happened while trying to print a previous panic message.1561 // A segfault happened while trying to print a previous panic message.
1585 // We're still holding the mutex but that's fine as we're going to1562 // We're still holding the mutex but that's fine as we're going to
1586 // call abort().1563 // 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 {};
1588 },1566 },
1589 else => {}, // Panicked while printing the recursive panic message.1567 else => {}, // Panicked while printing the recursive panic message.
1590 }1568 }
...@@ -1592,7 +1570,7 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex...@@ -1592,7 +1570,7 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
1592 // We cannot allow the signal handler to return because when it runs the original instruction1570 // We cannot allow the signal handler to return because when it runs the original instruction
1593 // again, the memory may be mapped and undefined behavior would occur rather than repeating1571 // again, the memory may be mapped and undefined behavior would occur rather than repeating
1594 // the segfault. So we simply abort here.1572 // the segfault. So we simply abort here.
1595 posix.abort();1573 std.process.abort();
1596}1574}
15971575
1598pub fn dumpStackPointerAddr(prefix: []const u8) void {1576pub fn dumpStackPointerAddr(prefix: []const u8) void {
...@@ -1616,20 +1594,14 @@ test "manage resources correctly" {...@@ -1616,20 +1594,14 @@ test "manage resources correctly" {
1616 return @returnAddress();1594 return @returnAddress();
1617 }1595 }
1618 };1596 };
1619 const gpa = std.testing.allocator;1597 const gpa = testing.allocator;
1620 var threaded: Io.Threaded = .init_single_threaded;1598 const io = testing.io;
1621 const io = threaded.ioBasic();1599
1622 var discarding: Io.Writer.Discarding = .init(&.{});1600 var discarding: Writer.Discarding = .init(&.{});
1623 var di: SelfInfo = .init;1601 var di: SelfInfo = .init;
1624 defer di.deinit(gpa);1602 defer di.deinit(gpa);
1625 try printSourceAtAddress(1603 const t: Io.Terminal = .{ .writer = &discarding.writer, .mode = .no_color };
1626 gpa,1604 try printSourceAtAddress(gpa, io, &di, t, S.showMyTrace());
1627 io,
1628 &di,
1629 &discarding.writer,
1630 S.showMyTrace(),
1631 .no_color,
1632 );
1633}1605}
16341606
1635/// This API helps you track where a value originated and where it was mutated,1607/// 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...@@ -1690,21 +1662,21 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1690 pub fn dump(t: @This()) void {1662 pub fn dump(t: @This()) void {
1691 if (!enabled) return;1663 if (!enabled) return;
16921664
1693 const stderr, const tty_config = lockStderrWriter(&.{});1665 const stderr = lockStderr(&.{}).terminal();
1694 defer unlockStderrWriter();1666 defer unlockStderr();
1695 const end = @min(t.index, size);1667 const end = @min(t.index, size);
1696 for (t.addrs[0..end], 0..) |frames_array, i| {1668 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;
1698 var frames_array_mutable = frames_array;1670 var frames_array_mutable = frames_array;
1699 const frames = mem.sliceTo(frames_array_mutable[0..], 0);1671 const frames = mem.sliceTo(frames_array_mutable[0..], 0);
1700 const stack_trace: StackTrace = .{1672 const stack_trace: StackTrace = .{
1701 .index = frames.len,1673 .index = frames.len,
1702 .instruction_addresses = frames,1674 .instruction_addresses = frames,
1703 };1675 };
1704 writeStackTrace(&stack_trace, stderr, tty_config) catch return;1676 writeStackTrace(&stack_trace, stderr) catch return;
1705 }1677 }
1706 if (t.index > end) {1678 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", .{
1708 t.index - end,1680 t.index - end,
1709 }) catch return;1681 }) catch return;
1710 }1682 }
lib/std/debug/ElfFile.zig+30-20
...@@ -1,5 +1,13 @@...@@ -1,5 +1,13 @@
1//! A helper type for loading an ELF file and collecting its DWARF debug information, unwind1//! A helper type for loading an ELF file and collecting its DWARF debug information, unwind
2//! information, and symbol table.2//! 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
4is_64: bool,12is_64: bool,
5endian: Endian,13endian: Endian,
...@@ -115,7 +123,8 @@ pub const LoadError = error{...@@ -115,7 +123,8 @@ pub const LoadError = error{
115123
116pub fn load(124pub fn load(
117 gpa: Allocator,125 gpa: Allocator,
118 elf_file: std.fs.File,126 io: Io,
127 elf_file: Io.File,
119 opt_build_id: ?[]const u8,128 opt_build_id: ?[]const u8,
120 di_search_paths: *const DebugInfoSearchPaths,129 di_search_paths: *const DebugInfoSearchPaths,
121) LoadError!ElfFile {130) LoadError!ElfFile {
...@@ -123,7 +132,7 @@ pub fn load(...@@ -123,7 +132,7 @@ pub fn load(
123 errdefer arena_instance.deinit();132 errdefer arena_instance.deinit();
124 const arena = arena_instance.allocator();133 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) {
127 error.CrcMismatch => unreachable, // we passed crc as null136 error.CrcMismatch => unreachable, // we passed crc as null
128 else => |e| return e,137 else => |e| return e,
129 };138 };
...@@ -148,7 +157,7 @@ pub fn load(...@@ -148,7 +157,7 @@ pub fn load(
148 if (build_id.len < 3) break :build_id;157 if (build_id.len < 3) break :build_id;
149158
150 for (di_search_paths.global_debug) |global_debug| {159 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", .{
152 global_debug,161 global_debug,
153 build_id[0..1],162 build_id[0..1],
154 build_id[1..],163 build_id[1..],
...@@ -156,7 +165,7 @@ pub fn load(...@@ -156,7 +165,7 @@ pub fn load(
156 }165 }
157166
158 if (di_search_paths.debuginfod_client) |components| {167 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", .{
160 components[0],169 components[0],
161 components[1],170 components[1],
162 build_id,171 build_id,
...@@ -173,18 +182,18 @@ pub fn load(...@@ -173,18 +182,18 @@ pub fn load(
173182
174 const exe_dir = di_search_paths.exe_dir orelse break :debug_link;183 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}", .{
177 exe_dir,186 exe_dir,
178 debug_filename,187 debug_filename,
179 })) |mapped| break :load_di mapped;188 })) |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}", .{
181 exe_dir,190 exe_dir,
182 debug_filename,191 debug_filename,
183 })) |mapped| break :load_di mapped;192 })) |mapped| break :load_di mapped;
184 for (di_search_paths.global_debug) |global_debug| {193 for (di_search_paths.global_debug) |global_debug| {
185 // This looks like a bug; it isn't. They really do embed the absolute path to the194 // This looks like a bug; it isn't. They really do embed the absolute path to the
186 // exe's dirname, *under* the global debug path.195 // 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}", .{
188 global_debug,197 global_debug,
189 exe_dir,198 exe_dir,
190 debug_filename,199 debug_filename,
...@@ -358,12 +367,19 @@ const Section = struct {...@@ -358,12 +367,19 @@ const Section = struct {
358 const Array = std.enums.EnumArray(Section.Id, ?Section);367 const Array = std.enums.EnumArray(Section.Id, ?Section);
359};368};
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 {
362 const path = try std.fmt.allocPrint(arena, fmt, args);378 const path = try std.fmt.allocPrint(arena, fmt, args);
363 const elf_file = std.fs.cwd().openFile(path, .{}) catch return null;379 const elf_file = Io.Dir.cwd().openFile(io, path, .{}) catch return null;
364 defer elf_file.close();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) {
367 error.OutOfMemory => |e| return e,383 error.OutOfMemory => |e| return e,
368 error.CrcMismatch => return null,384 error.CrcMismatch => return null,
369 else => return null,385 else => return null,
...@@ -408,13 +424,14 @@ const LoadInnerResult = struct {...@@ -408,13 +424,14 @@ const LoadInnerResult = struct {
408};424};
409fn loadInner(425fn loadInner(
410 arena: Allocator,426 arena: Allocator,
411 elf_file: std.fs.File,427 io: Io,
428 elf_file: Io.File,
412 opt_crc: ?u32,429 opt_crc: ?u32,
413) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult {430) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult {
414 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {431 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
415 const file_len = std.math.cast(432 const file_len = std.math.cast(
416 usize,433 usize,
417 elf_file.getEndPos() catch |err| switch (err) {434 elf_file.length(io) catch |err| switch (err) {
418 error.PermissionDenied => unreachable, // not asking for PROT_EXEC435 error.PermissionDenied => unreachable, // not asking for PROT_EXEC
419 else => |e| return e,436 else => |e| return e,
420 },437 },
...@@ -529,10 +546,3 @@ fn loadInner(...@@ -529,10 +546,3 @@ fn loadInner(
529 .mapped_mem = mapped_mem,546 .mapped_mem = mapped_mem,
530 };547 };
531}548}
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 @@...@@ -5,19 +5,18 @@
5//! Unlike `std.debug.SelfInfo`, this API does not assume the debug information5//! Unlike `std.debug.SelfInfo`, this API does not assume the debug information
6//! in question happens to match the host CPU architecture, OS, or other target6//! in question happens to match the host CPU architecture, OS, or other target
7//! properties.7//! properties.
8const Info = @This();
89
9const std = @import("../std.zig");10const std = @import("../std.zig");
11const Io = std.Io;
10const Allocator = std.mem.Allocator;12const Allocator = std.mem.Allocator;
11const Path = std.Build.Cache.Path;13const Path = std.Build.Cache.Path;
12const assert = std.debug.assert;14const assert = std.debug.assert;
13const Coverage = std.debug.Coverage;15const Coverage = std.debug.Coverage;
14const SourceLocation = std.debug.Coverage.SourceLocation;16const SourceLocation = std.debug.Coverage.SourceLocation;
15
16const ElfFile = std.debug.ElfFile;17const ElfFile = std.debug.ElfFile;
17const MachOFile = std.debug.MachOFile;18const MachOFile = std.debug.MachOFile;
1819
19const Info = @This();
20
21impl: union(enum) {20impl: union(enum) {
22 elf: ElfFile,21 elf: ElfFile,
23 macho: MachOFile,22 macho: MachOFile,
...@@ -25,15 +24,25 @@ impl: union(enum) {...@@ -25,15 +24,25 @@ impl: union(enum) {
25/// Externally managed, outlives this `Info` instance.24/// Externally managed, outlives this `Info` instance.
26coverage: *Coverage,25coverage: *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 {
31 switch (format) {40 switch (format) {
32 .elf => {41 .elf => {
33 var file = try path.root_dir.handle.openFile(path.sub_path, .{});42 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
34 defer file.close();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);
37 errdefer elf_file.deinit(gpa);46 errdefer elf_file.deinit(gpa);
3847
39 if (elf_file.dwarf == null) return error.MissingDebugInfo;48 if (elf_file.dwarf == null) return error.MissingDebugInfo;
...@@ -49,7 +58,7 @@ pub fn load(gpa: Allocator, path: Path, coverage: *Coverage, format: std.Target....@@ -49,7 +58,7 @@ pub fn load(gpa: Allocator, path: Path, coverage: *Coverage, format: std.Target.
49 const path_str = try path.toString(gpa);58 const path_str = try path.toString(gpa);
50 defer gpa.free(path_str);59 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);
53 errdefer macho_file.deinit(gpa);62 errdefer macho_file.deinit(gpa);
5463
55 return .{64 return .{
...@@ -76,6 +85,7 @@ pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError || error{U...@@ -76,6 +85,7 @@ pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError || error{U
76pub fn resolveAddresses(85pub fn resolveAddresses(
77 info: *Info,86 info: *Info,
78 gpa: Allocator,87 gpa: Allocator,
88 io: Io,
79 /// Asserts the addresses are in ascending order.89 /// Asserts the addresses are in ascending order.
80 sorted_pc_addrs: []const u64,90 sorted_pc_addrs: []const u64,
81 /// Asserts its length equals length of `sorted_pc_addrs`.91 /// Asserts its length equals length of `sorted_pc_addrs`.
...@@ -88,7 +98,7 @@ pub fn resolveAddresses(...@@ -88,7 +98,7 @@ pub fn resolveAddresses(
88 // Resolving all of the addresses at once unfortunately isn't so easy in Mach-O binaries98 // Resolving all of the addresses at once unfortunately isn't so easy in Mach-O binaries
89 // due to split debug information. For now, we'll just resolve the addreses one by one.99 // due to split debug information. For now, we'll just resolve the addreses one by one.
90 for (sorted_pc_addrs, output) |pc_addr, *src_loc| {100 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) {
92 error.InvalidMachO, error.InvalidDwarf => return error.InvalidDebugInfo,102 error.InvalidMachO, error.InvalidDwarf => return error.InvalidDebugInfo,
93 else => |e| return e,103 else => |e| return e,
94 };104 };
lib/std/debug/MachOFile.zig+11-11
...@@ -27,13 +27,13 @@ pub fn deinit(mf: *MachOFile, gpa: Allocator) void {...@@ -27,13 +27,13 @@ pub fn deinit(mf: *MachOFile, gpa: Allocator) void {
27 posix.munmap(mf.mapped_memory);27 posix.munmap(mf.mapped_memory);
28}28}
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 {
31 switch (arch) {31 switch (arch) {
32 .x86_64, .aarch64 => {},32 .x86_64, .aarch64 => {},
33 else => unreachable,33 else => unreachable,
34 }34 }
3535
36 const all_mapped_memory = try mapDebugInfoFile(path);36 const all_mapped_memory = try mapDebugInfoFile(io, path);
37 errdefer posix.munmap(all_mapped_memory);37 errdefer posix.munmap(all_mapped_memory);
3838
39 // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal39 // 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...@@ -239,7 +239,7 @@ pub fn load(gpa: Allocator, path: []const u8, arch: std.Target.Cpu.Arch) Error!M
239 .text_vmaddr = text_vmaddr,239 .text_vmaddr = text_vmaddr,
240 };240 };
241}241}
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 } {
243 const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo;243 const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo;
244244
245 if (symbol.ofile == Symbol.unknown_ofile) return error.MissingDebugInfo;245 if (symbol.ofile == Symbol.unknown_ofile) return error.MissingDebugInfo;
...@@ -254,7 +254,7 @@ pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, vaddr: u64) !struct {...@@ -254,7 +254,7 @@ pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, vaddr: u64) !struct {
254 const gop = try mf.ofiles.getOrPut(gpa, symbol.ofile);254 const gop = try mf.ofiles.getOrPut(gpa, symbol.ofile);
255 if (!gop.found_existing) {255 if (!gop.found_existing) {
256 const name = mem.sliceTo(mf.strings[symbol.ofile..], 0);256 const name = mem.sliceTo(mf.strings[symbol.ofile..], 0);
257 gop.value_ptr.* = loadOFile(gpa, name);257 gop.value_ptr.* = loadOFile(gpa, io, name);
258 }258 }
259 const of = &(gop.value_ptr.* catch |err| return err);259 const of = &(gop.value_ptr.* catch |err| return err);
260260
...@@ -356,7 +356,7 @@ test {...@@ -356,7 +356,7 @@ test {
356 _ = Symbol;356 _ = Symbol;
357}357}
358358
359fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {359fn loadOFile(gpa: Allocator, io: Io, o_file_name: []const u8) !OFile {
360 const all_mapped_memory, const mapped_ofile = map: {360 const all_mapped_memory, const mapped_ofile = map: {
361 const open_paren = paren: {361 const open_paren = paren: {
362 if (std.mem.endsWith(u8, o_file_name, ")")) {362 if (std.mem.endsWith(u8, o_file_name, ")")) {
...@@ -365,7 +365,7 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {...@@ -365,7 +365,7 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {
365 }365 }
366 }366 }
367 // Not an archive, just a normal path to a .o file367 // 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);
369 break :map .{ m, m };369 break :map .{ m, m };
370 };370 };
371371
...@@ -373,7 +373,7 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {...@@ -373,7 +373,7 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {
373373
374 const archive_path = o_file_name[0..open_paren];374 const archive_path = o_file_name[0..open_paren];
375 const target_name_in_archive = o_file_name[open_paren + 1 .. o_file_name.len - 1];375 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);
377 errdefer posix.munmap(mapped_archive);377 errdefer posix.munmap(mapped_archive);
378378
379 var ar_reader: Io.Reader = .fixed(mapped_archive);379 var ar_reader: Io.Reader = .fixed(mapped_archive);
...@@ -511,16 +511,16 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {...@@ -511,16 +511,16 @@ fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile {
511}511}
512512
513/// Uses `mmap` to map the file at `path` into memory.513/// Uses `mmap` to map the file at `path` into memory.
514fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {514fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 {
515 const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {515 const file = Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) {
516 error.FileNotFound => return error.MissingDebugInfo,516 error.FileNotFound => return error.MissingDebugInfo,
517 else => return error.ReadFailed,517 else => return error.ReadFailed,
518 };518 };
519 defer file.close();519 defer file.close(io);
520520
521 const file_len = std.math.cast(521 const file_len = std.math.cast(
522 usize,522 usize,
523 file.getEndPos() catch return error.ReadFailed,523 file.length(io) catch return error.ReadFailed,
524 ) orelse return error.ReadFailed;524 ) orelse return error.ReadFailed;
525525
526 return posix.mmap(526 return posix.mmap(
lib/std/debug/Pdb.zig+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const File = std.fs.File;2const File = std.Io.File;
3const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
4const pdb = std.pdb;4const pdb = std.pdb;
5const assert = std.debug.assert;5const assert = std.debug.assert;
lib/std/debug/SelfInfo/Elf.zig+17-18
...@@ -29,13 +29,12 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {...@@ -29,13 +29,12 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
29}29}
3030
31pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {31pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {
32 _ = io;
33 const module = try si.findModule(gpa, address, .exclusive);32 const module = try si.findModule(gpa, address, .exclusive);
34 defer si.rwlock.unlock();33 defer si.rwlock.unlock();
3534
36 const vaddr = address - module.load_offset;35 const vaddr = address - module.load_offset;
3736
38 const loaded_elf = try module.getLoadedElf(gpa);37 const loaded_elf = try module.getLoadedElf(gpa, io);
39 if (loaded_elf.file.dwarf) |*dwarf| {38 if (loaded_elf.file.dwarf) |*dwarf| {
40 if (!loaded_elf.scanned_dwarf) {39 if (!loaded_elf.scanned_dwarf) {
41 dwarf.open(gpa, native_endian) catch |err| switch (err) {40 dwarf.open(gpa, native_endian) catch |err| switch (err) {
...@@ -180,7 +179,7 @@ comptime {...@@ -180,7 +179,7 @@ comptime {
180 }179 }
181}180}
182pub const UnwindContext = Dwarf.SelfUnwinder;181pub 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 {
184 comptime assert(can_unwind);183 comptime assert(can_unwind);
185184
186 {185 {
...@@ -201,7 +200,7 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error...@@ -201,7 +200,7 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error
201 @memset(si.unwind_cache.?, .empty);200 @memset(si.unwind_cache.?, .empty);
202 }201 }
203202
204 const unwind_sections = try module.getUnwindSections(gpa);203 const unwind_sections = try module.getUnwindSections(gpa, io);
205 for (unwind_sections) |*unwind| {204 for (unwind_sections) |*unwind| {
206 if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| {205 if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| {
207 entry.populate(si.unwind_cache.?);206 entry.populate(si.unwind_cache.?);
...@@ -261,12 +260,12 @@ const Module = struct {...@@ -261,12 +260,12 @@ const Module = struct {
261 };260 };
262261
263 /// Assumes we already hold an exclusive lock.262 /// Assumes we already hold an exclusive lock.
264 fn getUnwindSections(mod: *Module, gpa: Allocator) Error![]Dwarf.Unwind {263 fn getUnwindSections(mod: *Module, gpa: Allocator, io: Io) Error![]Dwarf.Unwind {
265 if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa);264 if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa, io);
266 const us = &(mod.unwind.? catch |err| return err);265 const us = &(mod.unwind.? catch |err| return err);
267 return us.buf[0..us.len];266 return us.buf[0..us.len];
268 }267 }
269 fn loadUnwindSections(mod: *Module, gpa: Allocator) Error!UnwindSections {268 fn loadUnwindSections(mod: *Module, gpa: Allocator, io: Io) Error!UnwindSections {
270 var us: UnwindSections = .{269 var us: UnwindSections = .{
271 .buf = undefined,270 .buf = undefined,
272 .len = 0,271 .len = 0,
...@@ -284,7 +283,7 @@ const Module = struct {...@@ -284,7 +283,7 @@ const Module = struct {
284 } else {283 } else {
285 // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame`284 // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame`
286 // section, but we'll have to load the binary to get at it.285 // 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);
288 // If both are present, we can't just pick one -- the info could be split between them.287 // If both are present, we can't just pick one -- the info could be split between them.
289 // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one.288 // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one.
290 if (loaded.file.debug_frame) |*debug_frame| {289 if (loaded.file.debug_frame) |*debug_frame| {
...@@ -319,24 +318,24 @@ const Module = struct {...@@ -319,24 +318,24 @@ const Module = struct {
319 }318 }
320319
321 /// Assumes we already hold an exclusive lock.320 /// Assumes we already hold an exclusive lock.
322 fn getLoadedElf(mod: *Module, gpa: Allocator) Error!*LoadedElf {321 fn getLoadedElf(mod: *Module, gpa: Allocator, io: Io) Error!*LoadedElf {
323 if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa);322 if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io);
324 return if (mod.loaded_elf.?) |*elf| elf else |err| err;323 return if (mod.loaded_elf.?) |*elf| elf else |err| err;
325 }324 }
326 fn loadElf(mod: *Module, gpa: Allocator) Error!LoadedElf {325 fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf {
327 const load_result = if (mod.name.len > 0) res: {326 const load_result = if (mod.name.len > 0) res: {
328 var file = std.fs.cwd().openFile(mod.name, .{}) catch return error.MissingDebugInfo;327 var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo;
329 defer file.close();328 defer file.close(io);
330 break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name));329 break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name));
331 } else res: {330 } 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) {
333 error.OutOfMemory => |e| return e,332 error.OutOfMemory => |e| return e,
334 else => return error.ReadFailed,333 else => return error.ReadFailed,
335 };334 };
336 defer gpa.free(path);335 defer gpa.free(path);
337 var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo;336 var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo;
338 defer file.close();337 defer file.close(io);
339 break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path));338 break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path));
340 };339 };
341340
342 var elf_file = load_result catch |err| switch (err) {341 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 {...@@ -21,11 +21,10 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
21}21}
2222
23pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {23pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {
24 _ = io;
25 const module = try si.findModule(gpa, address);24 const module = try si.findModule(gpa, address);
26 defer si.mutex.unlock();25 defer si.mutex.unlock();
2726
28 const file = try module.getFile(gpa);27 const file = try module.getFile(gpa, io);
2928
30 // This is not necessarily the same as the vmaddr_slide that dyld would report. This is29 // This is not necessarily the same as the vmaddr_slide that dyld would report. This is
31 // because the segments in the file on disk might differ from the ones in memory. Normally30 // 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...@@ -39,7 +38,7 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st
3938
40 const vaddr = address - vaddr_offset;39 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 {
43 // Return at least the symbol name if available.42 // Return at least the symbol name if available.
44 return .{43 return .{
45 .name = try file.lookupSymbolName(vaddr),44 .name = try file.lookupSymbolName(vaddr),
...@@ -107,7 +106,8 @@ pub const UnwindContext = std.debug.Dwarf.SelfUnwinder;...@@ -107,7 +106,8 @@ pub const UnwindContext = std.debug.Dwarf.SelfUnwinder;
107/// Unwind a frame using MachO compact unwind info (from `__unwind_info`).106/// Unwind a frame using MachO compact unwind info (from `__unwind_info`).
108/// If the compact encoding can't encode a way to unwind a frame, it will107/// If the compact encoding can't encode a way to unwind a frame, it will
109/// defer unwinding to DWARF, in which case `__eh_frame` will be used if available.108/// 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;
111 return unwindFrameInner(si, gpa, context) catch |err| switch (err) {111 return unwindFrameInner(si, gpa, context) catch |err| switch (err) {
112 error.InvalidDebugInfo,112 error.InvalidDebugInfo,
113 error.MissingDebugInfo,113 error.MissingDebugInfo,
...@@ -546,12 +546,12 @@ const Module = struct {...@@ -546,12 +546,12 @@ const Module = struct {
546 };546 };
547 }547 }
548548
549 fn getFile(module: *Module, gpa: Allocator) Error!*MachOFile {549 fn getFile(module: *Module, gpa: Allocator, io: Io) Error!*MachOFile {
550 if (module.file == null) {550 if (module.file == null) {
551 const path = std.mem.span(551 const path = std.mem.span(
552 std.c.dyld_image_path_containing_address(@ptrFromInt(module.text_base)).?,552 std.c.dyld_image_path_containing_address(@ptrFromInt(module.text_base)).?,
553 );553 );
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) {
555 error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo,555 error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo,
556 error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e,556 error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e,
557 };557 };
...@@ -615,14 +615,14 @@ test {...@@ -615,14 +615,14 @@ test {
615}615}
616616
617/// Uses `mmap` to map the file at `path` into memory.617/// Uses `mmap` to map the file at `path` into memory.
618fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {618fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 {
619 const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {619 const file = Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) {
620 error.FileNotFound => return error.MissingDebugInfo,620 error.FileNotFound => return error.MissingDebugInfo,
621 else => return error.ReadFailed,621 else => return error.ReadFailed,
622 };622 };
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) {
626 error.Unexpected => |e| return e,626 error.Unexpected => |e| return e,
627 else => return error.ReadFailed,627 else => return error.ReadFailed,
628 };628 };
lib/std/debug/SelfInfo/Windows.zig+15-15
...@@ -149,8 +149,9 @@ pub const UnwindContext = struct {...@@ -149,8 +149,9 @@ pub const UnwindContext = struct {
149 return ctx.cur.getRegs().bp;149 return ctx.cur.getRegs().bp;
150 }150 }
151};151};
152pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {152pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize {
153 _ = si;153 _ = si;
154 _ = io;
154 _ = gpa;155 _ = gpa;
155156
156 const current_regs = context.cur.getRegs();157 const current_regs = context.cur.getRegs();
...@@ -204,14 +205,14 @@ const Module = struct {...@@ -204,14 +205,14 @@ const Module = struct {
204 coff_section_headers: []coff.SectionHeader,205 coff_section_headers: []coff.SectionHeader,
205206
206 const MappedFile = struct {207 const MappedFile = struct {
207 file: fs.File,208 file: Io.File,
208 section_handle: windows.HANDLE,209 section_handle: windows.HANDLE,
209 section_view: []const u8,210 section_view: []const u8,
210 fn deinit(mf: *const MappedFile) void {211 fn deinit(mf: *const MappedFile, io: Io) void {
211 const process_handle = windows.GetCurrentProcess();212 const process_handle = windows.GetCurrentProcess();
212 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mf.section_view.ptr)) == .SUCCESS);213 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mf.section_view.ptr)) == .SUCCESS);
213 windows.CloseHandle(mf.section_handle);214 windows.CloseHandle(mf.section_handle);
214 mf.file.close();215 mf.file.close(io);
215 }216 }
216 };217 };
217218
...@@ -222,7 +223,7 @@ const Module = struct {...@@ -222,7 +223,7 @@ const Module = struct {
222 pdb.file_reader.file.close(io);223 pdb.file_reader.file.close(io);
223 pdb.deinit();224 pdb.deinit();
224 }225 }
225 if (di.mapped_file) |*mf| mf.deinit();226 if (di.mapped_file) |*mf| mf.deinit(io);
226227
227 var arena = di.arena.promote(gpa);228 var arena = di.arena.promote(gpa);
228 arena.deinit();229 arena.deinit();
...@@ -314,8 +315,8 @@ const Module = struct {...@@ -314,8 +315,8 @@ const Module = struct {
314 );315 );
315 if (len == 0) return error.MissingDebugInfo;316 if (len == 0) return error.MissingDebugInfo;
316 const name_w = name_buffer[0 .. len + 4 :0];317 const name_w = name_buffer[0 .. len + 4 :0];
317 var threaded: Io.Threaded = .init_single_threaded;318 // TODO eliminate the reference to Io.Threaded.global_single_threaded here
318 const coff_file = threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {319 const coff_file = Io.Threaded.global_single_threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
319 error.Canceled => |e| return e,320 error.Canceled => |e| return e,
320 error.Unexpected => |e| return e,321 error.Unexpected => |e| return e,
321 error.FileNotFound => return error.MissingDebugInfo,322 error.FileNotFound => return error.MissingDebugInfo,
...@@ -331,7 +332,6 @@ const Module = struct {...@@ -331,7 +332,6 @@ const Module = struct {
331 error.SystemResources,332 error.SystemResources,
332 error.WouldBlock,333 error.WouldBlock,
333 error.AccessDenied,334 error.AccessDenied,
334 error.ProcessNotFound,
335 error.PermissionDenied,335 error.PermissionDenied,
336 error.NoSpaceLeft,336 error.NoSpaceLeft,
337 error.DeviceBusy,337 error.DeviceBusy,
...@@ -343,7 +343,7 @@ const Module = struct {...@@ -343,7 +343,7 @@ const Module = struct {
343 error.AntivirusInterference,343 error.AntivirusInterference,
344 error.ProcessFdQuotaExceeded,344 error.ProcessFdQuotaExceeded,
345 error.SystemFdQuotaExceeded,345 error.SystemFdQuotaExceeded,
346 error.FileLocksNotSupported,346 error.FileLocksUnsupported,
347 error.FileBusy,347 error.FileBusy,
348 => return error.ReadFailed,348 => return error.ReadFailed,
349 };349 };
...@@ -387,12 +387,12 @@ const Module = struct {...@@ -387,12 +387,12 @@ const Module = struct {
387 const section_view = section_view_ptr.?[0..coff_len];387 const section_view = section_view_ptr.?[0..coff_len];
388 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;388 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
389 break :mapped .{389 break :mapped .{
390 .file = .adaptFromNewApi(coff_file),390 .file = coff_file,
391 .section_handle = section_handle,391 .section_handle = section_handle,
392 .section_view = section_view,392 .section_view = section_view,
393 };393 };
394 };394 };
395 errdefer if (mapped_file) |*mf| mf.deinit();395 errdefer if (mapped_file) |*mf| mf.deinit(io);
396396
397 const coff_image_base = coff_obj.getImageBase();397 const coff_image_base = coff_obj.getImageBase();
398398
...@@ -432,22 +432,22 @@ const Module = struct {...@@ -432,22 +432,22 @@ const Module = struct {
432 break :pdb null;432 break :pdb null;
433 };433 };
434 const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: {434 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, .{});
436 } else res: {436 } 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) {
438 error.OutOfMemory, error.Unexpected => |e| return e,438 error.OutOfMemory, error.Unexpected => |e| return e,
439 else => return error.ReadFailed,439 else => return error.ReadFailed,
440 };440 };
441 defer gpa.free(self_dir);441 defer gpa.free(self_dir);
442 const abs_path = try fs.path.join(gpa, &.{ self_dir, path });442 const abs_path = try fs.path.join(gpa, &.{ self_dir, path });
443 defer gpa.free(abs_path);443 defer gpa.free(abs_path);
444 break :res std.fs.cwd().openFile(abs_path, .{});444 break :res Io.Dir.cwd().openFile(io, abs_path, .{});
445 };445 };
446 const pdb_file = pdb_file_open_result catch |err| switch (err) {446 const pdb_file = pdb_file_open_result catch |err| switch (err) {
447 error.FileNotFound, error.IsDir => break :pdb null,447 error.FileNotFound, error.IsDir => break :pdb null,
448 else => return error.ReadFailed,448 else => return error.ReadFailed,
449 };449 };
450 errdefer pdb_file.close();450 errdefer pdb_file.close(io);
451451
452 const pdb_reader = try arena.create(Io.File.Reader);452 const pdb_reader = try arena.create(Io.File.Reader);
453 pdb_reader.* = pdb_file.reader(io, try arena.alloc(u8, 4096));453 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");...@@ -14,9 +14,8 @@ const std = @import("../std.zig");
14pub fn call(msg: []const u8, ra: ?usize) noreturn {14pub fn call(msg: []const u8, ra: ?usize) noreturn {
15 @branchHint(.cold);15 @branchHint(.cold);
16 _ = ra;16 _ = ra;
17 std.debug.lockStdErr();17 const stderr_writer = &std.debug.lockStderr(&.{}).file_writer.interface;
18 const stderr: std.fs.File = .stderr();18 stderr_writer.writeAll(msg) catch {};
19 stderr.writeAll(msg) catch {};
20 @trap();19 @trap();
21}20}
2221
lib/std/dynamic_library.zig+39-37
...@@ -1,10 +1,12 @@...@@ -1,10 +1,12 @@
1const std = @import("std.zig");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3
4const std = @import("std.zig");
5const Io = std.Io;
3const mem = std.mem;6const mem = std.mem;
4const testing = std.testing;7const testing = std.testing;
5const elf = std.elf;8const elf = std.elf;
6const windows = std.os.windows;9const windows = std.os.windows;
7const native_os = builtin.os.tag;
8const posix = std.posix;10const posix = std.posix;
911
10/// Cross-platform dynamic library loading and symbol lookup.12/// Cross-platform dynamic library loading and symbol lookup.
...@@ -53,11 +55,11 @@ pub const DynLib = struct {...@@ -53,11 +55,11 @@ pub const DynLib = struct {
53// An iterator is provided in order to traverse the linked list in a idiomatic55// An iterator is provided in order to traverse the linked list in a idiomatic
54// fashion.56// fashion.
55const LinkMap = extern struct {57const LinkMap = extern struct {
56 l_addr: usize,58 addr: usize,
57 l_name: [*:0]const u8,59 name: [*:0]const u8,
58 l_ld: ?*elf.Dyn,60 ld: ?*elf.Dyn,
59 l_next: ?*LinkMap,61 next: ?*LinkMap,
60 l_prev: ?*LinkMap,62 prev: ?*LinkMap,
6163
62 pub const Iterator = struct {64 pub const Iterator = struct {
63 current: ?*LinkMap,65 current: ?*LinkMap,
...@@ -68,7 +70,7 @@ const LinkMap = extern struct {...@@ -68,7 +70,7 @@ const LinkMap = extern struct {
6870
69 pub fn next(self: *Iterator) ?*LinkMap {71 pub fn next(self: *Iterator) ?*LinkMap {
70 if (self.current) |it| {72 if (self.current) |it| {
71 self.current = it.l_next;73 self.current = it.next;
72 return it;74 return it;
73 }75 }
74 return null;76 return null;
...@@ -77,10 +79,10 @@ const LinkMap = extern struct {...@@ -77,10 +79,10 @@ const LinkMap = extern struct {
77};79};
7880
79const RDebug = extern struct {81const RDebug = extern struct {
80 r_version: i32,82 version: i32,
81 r_map: ?*LinkMap,83 map: ?*LinkMap,
82 r_brk: usize,84 brk: usize,
83 r_ldbase: usize,85 ldbase: usize,
84};86};
8587
86/// TODO fix comparisons of extern symbol pointers so we don't need this helper function.88/// 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 {...@@ -105,8 +107,8 @@ pub fn linkmap_iterator() error{InvalidExe}!LinkMap.Iterator {
105 elf.DT_DEBUG => {107 elf.DT_DEBUG => {
106 const ptr = @as(?*RDebug, @ptrFromInt(_DYNAMIC[i].d_val));108 const ptr = @as(?*RDebug, @ptrFromInt(_DYNAMIC[i].d_val));
107 if (ptr) |r_debug| {109 if (ptr) |r_debug| {
108 if (r_debug.r_version != 1) return error.InvalidExe;110 if (r_debug.version != 1) return error.InvalidExe;
109 break :init r_debug.r_map;111 break :init r_debug.map;
110 }112 }
111 },113 },
112 elf.DT_PLTGOT => {114 elf.DT_PLTGOT => {
...@@ -155,24 +157,24 @@ pub const ElfDynLib = struct {...@@ -155,24 +157,24 @@ pub const ElfDynLib = struct {
155 dt_gnu_hash: *elf.gnu_hash.Header,157 dt_gnu_hash: *elf.gnu_hash.Header,
156 };158 };
157159
158 fn openPath(path: []const u8) !std.fs.Dir {160 fn openPath(io: Io, path: []const u8) !Io.Dir {
159 if (path.len == 0) return error.NotDir;161 if (path.len == 0) return error.NotDir;
160 var parts = std.mem.tokenizeScalar(u8, path, '/');162 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();
162 while (parts.next()) |part| {164 while (parts.next()) |part| {
163 const child = try parent.openDir(part, .{});165 const child = try parent.openDir(io, part, .{});
164 parent.close();166 parent.close(io);
165 parent = child;167 parent = child;
166 }168 }
167 return parent;169 return parent;
168 }170 }
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 {
171 var paths = std.mem.tokenizeScalar(u8, search_path, delim);173 var paths = std.mem.tokenizeScalar(u8, search_path, delim);
172 while (paths.next()) |p| {174 while (paths.next()) |p| {
173 var dir = openPath(p) catch continue;175 var dir = openPath(io, p) catch continue;
174 defer dir.close();176 defer dir.close(io);
175 const fd = posix.openat(dir.fd, file_name, .{177 const fd = posix.openat(dir.handle, file_name, .{
176 .ACCMODE = .RDONLY,178 .ACCMODE = .RDONLY,
177 .CLOEXEC = true,179 .CLOEXEC = true,
178 }, 0) catch continue;180 }, 0) catch continue;
...@@ -181,10 +183,10 @@ pub const ElfDynLib = struct {...@@ -181,10 +183,10 @@ pub const ElfDynLib = struct {
181 return null;183 return null;
182 }184 }
183185
184 fn resolveFromParent(dir_path: []const u8, file_name: []const u8) ?posix.fd_t {186 fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?posix.fd_t {
185 var dir = std.fs.cwd().openDir(dir_path, .{}) catch return null;187 var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch return null;
186 defer dir.close();188 defer dir.close(io);
187 return posix.openat(dir.fd, file_name, .{189 return posix.openat(dir.handle, file_name, .{
188 .ACCMODE = .RDONLY,190 .ACCMODE = .RDONLY,
189 .CLOEXEC = true,191 .CLOEXEC = true,
190 }, 0) catch null;192 }, 0) catch null;
...@@ -195,7 +197,7 @@ pub const ElfDynLib = struct {...@@ -195,7 +197,7 @@ pub const ElfDynLib = struct {
195 // - DT_RPATH of the calling binary is not used as a search path197 // - DT_RPATH of the calling binary is not used as a search path
196 // - DT_RUNPATH of the calling binary is not used as a search path198 // - DT_RUNPATH of the calling binary is not used as a search path
197 // - /etc/ld.so.cache is not read199 // - /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 {
199 // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname201 // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname
200 if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| {202 if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| {
201 return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);203 return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
...@@ -206,25 +208,27 @@ pub const ElfDynLib = struct {...@@ -206,25 +208,27 @@ pub const ElfDynLib = struct {
206 std.os.linux.getegid() == std.os.linux.getgid())208 std.os.linux.getegid() == std.os.linux.getgid())
207 {209 {
208 if (posix.getenvZ("LD_LIBRARY_PATH")) |ld_library_path| {210 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| {
210 return fd;212 return fd;
211 }213 }
212 }214 }
213 }215 }
214216
215 // Lastly the directories /lib and /usr/lib are searched (in this exact order)217 // Lastly the directories /lib and /usr/lib are searched (in this exact order)
216 if (resolveFromParent("/lib", path_or_name)) |fd| return fd;218 if (resolveFromParent(io, "/lib", path_or_name)) |fd| return fd;
217 if (resolveFromParent("/usr/lib", path_or_name)) |fd| return fd;219 if (resolveFromParent(io, "/usr/lib", path_or_name)) |fd| return fd;
218 return error.FileNotFound;220 return error.FileNotFound;
219 }221 }
220222
221 /// Trusts the file. Malicious file will be able to execute arbitrary code.223 /// Trusts the file. Malicious file will be able to execute arbitrary code.
222 pub fn open(path: []const u8) Error!ElfDynLib {224 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);
224 defer posix.close(fd);228 defer posix.close(fd);
225229
226 const file: std.fs.File = .{ .handle = fd };230 const file: Io.File = .{ .handle = fd };
227 const stat = try file.stat();231 const stat = try file.stat(io);
228 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;232 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
229233
230 const page_size = std.heap.pageSize();234 const page_size = std.heap.pageSize();
...@@ -549,11 +553,9 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: elf.Versym, vername: []const u8, str...@@ -549,11 +553,9 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: elf.Versym, vername: []const u8, str
549}553}
550554
551test "ElfDynLib" {555test "ElfDynLib" {
552 if (native_os != .linux) {556 if (native_os != .linux) return error.SkipZigTest;
553 return error.SkipZigTest;
554 }
555
556 try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so"));557 try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so"));
558 try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so"));
557}559}
558560
559/// Separated to avoid referencing `WindowsDynLib`, because its field types may not561/// Separated to avoid referencing `WindowsDynLib`, because its field types may not
lib/std/fs.zig+7-555
...@@ -1,576 +1,28 @@...@@ -1,576 +1,28 @@
1//! File System.1//! File System.
2const builtin = @import("builtin");
3const native_os = builtin.os.tag;
42
5const std = @import("std.zig");3const 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();5/// Deprecated, use `std.Io.Dir.path`.
17
18pub const AtomicFile = @import("fs/AtomicFile.zig");
19pub const Dir = @import("fs/Dir.zig");
20pub const File = @import("fs/File.zig");
21pub const path = @import("fs/path.zig");6pub const path = @import("fs/path.zig");
22
23pub const has_executable_bit = switch (native_os) {
24 .windows, .wasi => false,
25 else => true,
26};
27
28pub const wasi = @import("fs/wasi.zig");7pub 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
37pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;9pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
38pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;10pub 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
93pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*;12pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*;
9413
95/// Base64 encoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.14/// 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
98/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.17/// 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);18pub const base64_decoder = std.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,
38119
382 /// On Windows, antivirus software is enabled by default. It can be20/// Deprecated, use `std.Io.Dir.max_path_bytes`.
383 /// disabled, but Windows Update sometimes ignores the user's preference21pub const max_path_bytes = std.Io.Dir.max_path_bytes;
384 /// and re-enables it. When enabled, antivirus software on Windows22/// Deprecated, use `std.Io.Dir.max_name_bytes`.
385 /// intercepts file system operations and makes them significantly slower23pub const max_name_bytes = std.Io.Dir.max_name_bytes;
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}
56924
570test {25test {
571 _ = AtomicFile;
572 _ = Dir;
573 _ = File;
574 _ = path;26 _ = path;
575 _ = @import("fs/test.zig");27 _ = @import("fs/test.zig");
576 _ = @import("fs/get_app_data_dir.zig");28 _ = @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!...@@ -872,7 +872,7 @@ pub fn resolve(allocator: Allocator, paths: []const []const u8) Allocator.Error!
872872
873/// This function is like a series of `cd` statements executed one after another.873/// This function is like a series of `cd` statements executed one after another.
874/// It resolves "." and ".." to the best of its ability, but will not convert relative paths to874/// 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.
876/// ".." components may persist in the resolved path if the resolved path is relative or drive-relative.876/// ".." components may persist in the resolved path if the resolved path is relative or drive-relative.
877/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.877/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.
878///878///
...@@ -1095,7 +1095,7 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator...@@ -1095,7 +1095,7 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator
10951095
1096/// This function is like a series of `cd` statements executed one after another.1096/// This function is like a series of `cd` statements executed one after another.
1097/// It resolves "." and ".." to the best of its ability, but will not convert relative paths to1097/// 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.
1099/// ".." components may persist in the resolved path if the resolved path is relative.1099/// ".." components may persist in the resolved path if the resolved path is relative.
1100/// The result does not have a trailing path separator.1100/// The result does not have a trailing path separator.
1101/// This function does not perform any syscalls. Executing this series of path1101/// 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;...@@ -3,66 +3,89 @@ const native_os = builtin.os.tag;
33
4const std = @import("../std.zig");4const std = @import("../std.zig");
5const Io = std.Io;5const Io = std.Io;
6const testing = std.testing;
7const fs = std.fs;
8const mem = std.mem;6const mem = std.mem;
7const Allocator = std.mem.Allocator;
9const wasi = std.os.wasi;8const wasi = std.os.wasi;
10const windows = std.os.windows;9const windows = std.os.windows;
11const posix = std.posix;
12
13const ArenaAllocator = std.heap.ArenaAllocator;10const ArenaAllocator = std.heap.ArenaAllocator;
14const Dir = std.fs.Dir;11const Dir = std.Io.Dir;
15const File = std.fs.File;12const File = std.Io.File;
16const tmpDir = testing.tmpDir;13const SymLinkFlags = std.Io.Dir.SymLinkFlags;
17const SymLinkFlags = std.fs.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
19const PathType = enum {23const PathType = enum {
20 relative,24 relative,
21 absolute,25 absolute,
22 unc,26 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 {
25 return switch (self) {29 return switch (self) {
26 .relative => true,30 .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 },
28 .unc => target_os.tag == .windows,50 .unc => target_os.tag == .windows,
29 };51 };
30 }52 }
3153
32 pub const TransformError = posix.RealPathError || error{OutOfMemory};54 const TransformError = Dir.RealPathError || error{OutOfMemory};
33 pub const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8;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 {
36 switch (path_type) {58 switch (path_type) {
37 .relative => return struct {59 .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 {
39 _ = allocator;61 _ = allocator;
62 _ = io;
40 _ = dir;63 _ = dir;
41 return relative_path;64 return relative_path;
42 }65 }
43 }.transform,66 }.transform,
44 .absolute => return struct {67 .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 {
46 // The final path may not actually exist which would cause realpath to fail.69 // The final path may not actually exist which would cause realpath to fail.
47 // So instead, we get the path of the dir and join it with the relative path.70 // 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;71 var fd_path_buf: [Dir.max_path_bytes]u8 = undefined;
49 const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf);72 const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)];
50 return fs.path.joinZ(allocator, &.{ dir_path, relative_path });73 return Dir.path.joinZ(allocator, &.{ dir_path, relative_path });
51 }74 }
52 }.transform,75 }.transform,
53 .unc => return struct {76 .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 {
55 // Any drive absolute path (C:\foo) can be converted into a UNC path by78 // Any drive absolute path (C:\foo) can be converted into a UNC path by
56 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.79 // 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;80 var fd_path_buf: [Dir.max_path_bytes]u8 = undefined;
58 const dir_path = try std.os.getFdPath(dir.fd, &fd_path_buf);81 const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)];
59 const windows_path_type = windows.getWin32PathType(u8, dir_path);82 const windows_path_type = windows.getWin32PathType(u8, dir_path);
60 switch (windows_path_type) {83 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 }),
62 .drive_absolute => {85 .drive_absolute => {
63 // `C:\<...>` -> `\\127.0.0.1\C$\<...>`86 // `C:\<...>` -> `\\127.0.0.1\C$\<...>`
64 const prepended = "\\\\127.0.0.1\\";87 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 });
66 path[prepended.len + 1] = '$';89 path[prepended.len + 1] = '$';
67 return path;90 return path;
68 },91 },
...@@ -80,10 +103,10 @@ const TestContext = struct {...@@ -80,10 +103,10 @@ const TestContext = struct {
80 path_sep: u8,103 path_sep: u8,
81 arena: ArenaAllocator,104 arena: ArenaAllocator,
82 tmp: testing.TmpDir,105 tmp: testing.TmpDir,
83 dir: std.fs.Dir,106 dir: Dir,
84 transform_fn: *const PathType.TransformFn,107 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 {
87 const tmp = tmpDir(.{ .iterate = true });110 const tmp = tmpDir(.{ .iterate = true });
88 return .{111 return .{
89 .io = testing.io,112 .io = testing.io,
...@@ -107,7 +130,7 @@ const TestContext = struct {...@@ -107,7 +130,7 @@ const TestContext = struct {
107 /// `TestContext.deinit`.130 /// `TestContext.deinit`.
108 pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 {131 pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 {
109 const allocator = self.arena.allocator();132 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);
111 if (native_os == .windows) {134 if (native_os == .windows) {
112 const transformed_sep_path = try allocator.dupeZ(u8, transformed_path);135 const transformed_sep_path = try allocator.dupeZ(u8, transformed_path);
113 std.mem.replaceScalar(u8, transformed_sep_path, switch (self.path_sep) {136 std.mem.replaceScalar(u8, transformed_sep_path, switch (self.path_sep) {
...@@ -150,7 +173,7 @@ fn testWithAllSupportedPathTypes(test_func: anytype) !void {...@@ -150,7 +173,7 @@ fn testWithAllSupportedPathTypes(test_func: anytype) !void {
150173
151fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: u8, test_func: anytype) !void {174fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep: u8, test_func: anytype) !void {
152 if (!(comptime path_type.isSupported(builtin.os))) return;175 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
155 var ctx = TestContext.init(path_type, path_sep, testing.allocator, path_type.getTransformFn());178 var ctx = TestContext.init(path_type, path_sep, testing.allocator, path_type.getTransformFn());
156 defer ctx.deinit();179 defer ctx.deinit();
...@@ -160,8 +183,8 @@ fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep:...@@ -160,8 +183,8 @@ fn testWithPathTypeIfSupported(comptime path_type: PathType, comptime path_sep:
160183
161// For use in test setup. If the symlink creation fails on Windows with184// For use in test setup. If the symlink creation fails on Windows with
162// AccessDenied, then make the test failure silent (it is not a Zig failure).185// 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 {186fn setupSymlink(io: Io, dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFlags) !void {
164 return dir.symLink(target, link, flags) catch |err| switch (err) {187 return dir.symLink(io, target, link, flags) catch |err| switch (err) {
165 // Symlink requires admin privileges on windows, so this test can legitimately fail.188 // Symlink requires admin privileges on windows, so this test can legitimately fail.
166 error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err,189 error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err,
167 else => return err,190 else => return err,
...@@ -170,50 +193,43 @@ fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFl...@@ -170,50 +193,43 @@ fn setupSymlink(dir: Dir, target: []const u8, link: []const u8, flags: SymLinkFl
170193
171// For use in test setup. If the symlink creation fails on Windows with194// For use in test setup. If the symlink creation fails on Windows with
172// AccessDenied, then make the test failure silent (it is not a Zig failure).195// AccessDenied, then make the test failure silent (it is not a Zig failure).
173fn setupSymlinkAbsolute(target: []const u8, link: []const u8, flags: SymLinkFlags) !void {196fn setupSymlinkAbsolute(io: Io, target: []const u8, link: []const u8, flags: SymLinkFlags) !void {
174 return fs.symLinkAbsolute(target, link, flags) catch |err| switch (err) {197 return Dir.symLinkAbsolute(io, target, link, flags) catch |err| switch (err) {
175 error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err,198 error.AccessDenied => if (native_os == .windows) return error.SkipZigTest else return err,
176 else => return err,199 else => return err,
177 };200 };
178}201}
179202
180test "Dir.readLink" {203test "Dir.readLink" {
204 const io = testing.io;
205
181 try testWithAllSupportedPathTypes(struct {206 try testWithAllSupportedPathTypes(struct {
182 fn impl(ctx: *TestContext) !void {207 fn impl(ctx: *TestContext) !void {
183 // Create some targets208 // Create some targets
184 const file_target_path = try ctx.transformPath("file.txt");209 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" });
186 const dir_target_path = try ctx.transformPath("subdir");211 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
189 // On Windows, symlink targets always use the canonical path separator214 // On Windows, symlink targets always use the canonical path separator
190 const canonical_file_target_path = try ctx.toCanonicalPathSep(file_target_path);215 const canonical_file_target_path = try ctx.toCanonicalPathSep(file_target_path);
191 const canonical_dir_target_path = try ctx.toCanonicalPathSep(dir_target_path);216 const canonical_dir_target_path = try ctx.toCanonicalPathSep(dir_target_path);
192217
193 // test 1: symlink to a file218 // test 1: symlink to a file
194 try setupSymlink(ctx.dir, file_target_path, "symlink1", .{});219 try setupSymlink(io, ctx.dir, file_target_path, "symlink1", .{});
195 try testReadLink(ctx.dir, canonical_file_target_path, "symlink1");220 try testReadLink(io, 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 }
199221
200 // test 2: symlink to a directory (can be different on Windows)222 // test 2: symlink to a directory (can be different on Windows)
201 try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true });223 try setupSymlink(io, ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true });
202 try testReadLink(ctx.dir, canonical_dir_target_path, "symlink2");224 try testReadLink(io, 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 }
206225
207 // test 3: relative path symlink226 // 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";
209 const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file);228 const canonical_parent_file = try ctx.toCanonicalPathSep(parent_file);
210 var subdir = try ctx.dir.makeOpenPath("subdir", .{});229 var subdir = try ctx.dir.createDirPathOpen(io, "subdir", .{});
211 defer subdir.close();230 defer subdir.close(io);
212 try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{});231 try setupSymlink(io, subdir, canonical_parent_file, "relative-link.txt", .{});
213 try testReadLink(subdir, canonical_parent_file, "relative-link.txt");232 try testReadLink(io, 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 }
217 }233 }
218 }.impl);234 }.impl);
219}235}
...@@ -221,55 +237,39 @@ test "Dir.readLink" {...@@ -221,55 +237,39 @@ test "Dir.readLink" {
221test "Dir.readLink on non-symlinks" {237test "Dir.readLink on non-symlinks" {
222 try testWithAllSupportedPathTypes(struct {238 try testWithAllSupportedPathTypes(struct {
223 fn impl(ctx: *TestContext) !void {239 fn impl(ctx: *TestContext) !void {
240 const io = ctx.io;
224 const file_path = try ctx.transformPath("file.txt");241 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" });
226 const dir_path = try ctx.transformPath("subdir");243 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
229 // file246 // file
230 var buffer: [fs.max_path_bytes]u8 = undefined;247 var buffer: [Dir.max_path_bytes]u8 = undefined;
231 try std.testing.expectError(error.NotLink, ctx.dir.readLink(file_path, &buffer));248 try std.testing.expectError(error.NotLink, ctx.dir.readLink(io, 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 }
236249
237 // dir250 // dir
238 try std.testing.expectError(error.NotLink, ctx.dir.readLink(dir_path, &buffer));251 try std.testing.expectError(error.NotLink, ctx.dir.readLink(io, 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 }
243 }252 }
244 }.impl);253 }.impl);
245}254}
246255
247fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {256fn testReadLink(io: Io, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
248 var buffer: [fs.max_path_bytes]u8 = undefined;257 var buffer: [Dir.max_path_bytes]u8 = undefined;
249 const actual = try dir.readLink(symlink_path, buffer[0..]);258 const actual = buffer[0..try dir.readLink(io, symlink_path, &buffer)];
250 try testing.expectEqualStrings(target_path, actual);259 try 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);
262}260}
263261
264fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {262fn testReadLinkAbsolute(io: Io, target_path: []const u8, symlink_path: []const u8) !void {
265 var buffer: [fs.max_path_bytes]u8 = undefined;263 var buffer: [Dir.max_path_bytes]u8 = undefined;
266 const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]);264 const given = buffer[0..try Dir.readLinkAbsolute(io, symlink_path, &buffer)];
267 try testing.expectEqualStrings(target_path, given);265 try expectEqualStrings(target_path, given);
268}266}
269267
270test "File.stat on a File that is a symlink returns Kind.sym_link" {268test "File.stat on a File that is a symlink returns Kind.sym_link" {
271 // This test requires getting a file descriptor of a symlink which269 const io = testing.io;
272 // is not possible on all targets270
271 // This test requires getting a file descriptor of a symlink which is not
272 // possible on all targets.
273 switch (builtin.target.os.tag) {273 switch (builtin.target.os.tag) {
274 .windows, .linux => {},274 .windows, .linux => {},
275 else => return error.SkipZigTest,275 else => return error.SkipZigTest,
...@@ -278,99 +278,35 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" {...@@ -278,99 +278,35 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" {
278 try testWithAllSupportedPathTypes(struct {278 try testWithAllSupportedPathTypes(struct {
279 fn impl(ctx: *TestContext) !void {279 fn impl(ctx: *TestContext) !void {
280 const dir_target_path = try ctx.transformPath("subdir");280 const dir_target_path = try ctx.transformPath("subdir");
281 try ctx.dir.makeDir(dir_target_path);281 try ctx.dir.createDir(io, dir_target_path, .default_dir);
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 );
334282
335 switch (rc) {283 try setupSymlink(io, ctx.dir, dir_target_path, "symlink", .{ .is_directory = true });
336 .SUCCESS => break :windows_symlink .{ .fd = handle },284
337 else => return windows.unexpectedStatus(rc),285 var symlink: File = try ctx.dir.openFile(io, "symlink", .{
338 }286 .follow_symlinks = false,
339 },287 .path_only = true,
340 .linux => linux_symlink: {288 });
341 const sub_path_c = try posix.toPosixPath("symlink");289 defer symlink.close(io);
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();
356290
357 const stat = try symlink.stat();291 const stat = try symlink.stat(io);
358 try testing.expectEqual(File.Kind.sym_link, stat.kind);292 try expectEqual(File.Kind.sym_link, stat.kind);
359 }293 }
360 }.impl);294 }.impl);
361}295}
362296
363test "openDir" {297test "openDir" {
298 const io = testing.io;
299
364 try testWithAllSupportedPathTypes(struct {300 try testWithAllSupportedPathTypes(struct {
365 fn impl(ctx: *TestContext) !void {301 fn impl(ctx: *TestContext) !void {
366 const allocator = ctx.arena.allocator();302 const allocator = ctx.arena.allocator();
367 const subdir_path = try ctx.transformPath("subdir");303 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
370 for ([_][]const u8{ "", ".", ".." }) |sub_path| {306 for ([_][]const u8{ "", ".", ".." }) |sub_path| {
371 const dir_path = try fs.path.join(allocator, &.{ subdir_path, sub_path });307 const dir_path = try Dir.path.join(allocator, &.{ subdir_path, sub_path });
372 var dir = try ctx.dir.openDir(dir_path, .{});308 var dir = try ctx.dir.openDir(io, dir_path, .{});
373 defer dir.close();309 defer dir.close(io);
374 }310 }
375 }311 }
376 }.impl);312 }.impl);
...@@ -380,79 +316,87 @@ test "accessAbsolute" {...@@ -380,79 +316,87 @@ test "accessAbsolute" {
380 if (native_os == .wasi) return error.SkipZigTest;316 if (native_os == .wasi) return error.SkipZigTest;
381 if (native_os == .openbsd) return error.SkipZigTest;317 if (native_os == .openbsd) return error.SkipZigTest;
382318
319 const io = testing.io;
320 const gpa = testing.allocator;
321
383 var tmp = tmpDir(.{});322 var tmp = tmpDir(.{});
384 defer tmp.cleanup();323 defer tmp.cleanup();
385324
386 const base_path = try tmp.dir.realpathAlloc(testing.allocator, ".");325 const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
387 defer testing.allocator.free(base_path);326 defer gpa.free(base_path);
388327
389 try fs.accessAbsolute(base_path, .{});328 try Dir.accessAbsolute(io, base_path, .{});
390}329}
391330
392test "openDirAbsolute" {331test "openDirAbsolute" {
393 if (native_os == .wasi) return error.SkipZigTest;332 if (native_os == .wasi) return error.SkipZigTest;
394 if (native_os == .openbsd) return error.SkipZigTest;333 if (native_os == .openbsd) return error.SkipZigTest;
395334
335 const io = testing.io;
336 const gpa = testing.allocator;
337
396 var tmp = tmpDir(.{});338 var tmp = tmpDir(.{});
397 defer tmp.cleanup();339 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");343 try tmp.dir.createDir(io, "subdir", .default_dir);
402 const sub_path = try tmp.dir.realpathAlloc(testing.allocator, "subdir");344 const sub_path = try tmp.dir.realPathFileAlloc(io, "subdir", gpa);
403 defer testing.allocator.free(sub_path);345 defer gpa.free(sub_path);
404346
405 // Can open sub_path347 // Can open sub_path
406 var tmp_sub = try fs.openDirAbsolute(sub_path, .{});348 var tmp_sub = try Dir.openDirAbsolute(io, sub_path, .{});
407 defer tmp_sub.close();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
411 {353 {
412 // Can open sub_path + ".."354 // 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, ".." });
414 defer testing.allocator.free(dir_path);356 defer testing.allocator.free(dir_path);
415357
416 var dir = try fs.openDirAbsolute(dir_path, .{});358 var dir = try Dir.openDirAbsolute(io, dir_path, .{});
417 defer dir.close();359 defer dir.close(io);
418360
419 const ino = (try dir.stat()).inode;361 const ino = (try dir.stat(io)).inode;
420 try testing.expectEqual(tmp_ino, ino);362 try expectEqual(tmp_ino, ino);
421 }363 }
422364
423 {365 {
424 // Can open sub_path + "."366 // 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, "." });
426 defer testing.allocator.free(dir_path);368 defer testing.allocator.free(dir_path);
427369
428 var dir = try fs.openDirAbsolute(dir_path, .{});370 var dir = try Dir.openDirAbsolute(io, dir_path, .{});
429 defer dir.close();371 defer dir.close(io);
430372
431 const ino = (try dir.stat()).inode;373 const ino = (try dir.stat(io)).inode;
432 try testing.expectEqual(sub_ino, ino);374 try expectEqual(sub_ino, ino);
433 }375 }
434376
435 {377 {
436 // Can open subdir + "..", with some extra "."378 // 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, ".", "..", "." });
438 defer testing.allocator.free(dir_path);380 defer testing.allocator.free(dir_path);
439381
440 var dir = try fs.openDirAbsolute(dir_path, .{});382 var dir = try Dir.openDirAbsolute(io, dir_path, .{});
441 defer dir.close();383 defer dir.close(io);
442384
443 const ino = (try dir.stat()).inode;385 const ino = (try dir.stat(io)).inode;
444 try testing.expectEqual(tmp_ino, ino);386 try expectEqual(tmp_ino, ino);
445 }387 }
446}388}
447389
448test "openDir cwd parent '..'" {390test "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| {
450 if (native_os == .wasi and err == error.PermissionDenied) {394 if (native_os == .wasi and err == error.PermissionDenied) {
451 return; // This is okay. WASI disallows escaping from the fs sandbox395 return; // This is okay. WASI disallows escaping from the fs sandbox
452 }396 }
453 return err;397 return err;
454 };398 };
455 defer dir.close();399 defer dir.close(io);
456}400}
457401
458test "openDir non-cwd parent '..'" {402test "openDir non-cwd parent '..'" {
...@@ -461,69 +405,76 @@ test "openDir non-cwd parent '..'" {...@@ -461,69 +405,76 @@ test "openDir non-cwd parent '..'" {
461 else => {},405 else => {},
462 }406 }
463407
408 const io = testing.io;
409 const gpa = testing.allocator;
410
464 var tmp = tmpDir(.{});411 var tmp = tmpDir(.{});
465 defer tmp.cleanup();412 defer tmp.cleanup();
466413
467 var subdir = try tmp.dir.makeOpenPath("subdir", .{});414 var subdir = try tmp.dir.createDirPathOpen(io, "subdir", .{});
468 defer subdir.close();415 defer subdir.close(io);
469416
470 var dir = try subdir.openDir("..", .{});417 var dir = try subdir.openDir(io, "..", .{});
471 defer dir.close();418 defer dir.close(io);
472419
473 const expected_path = try tmp.dir.realpathAlloc(testing.allocator, ".");420 const expected_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
474 defer testing.allocator.free(expected_path);421 defer gpa.free(expected_path);
475422
476 const actual_path = try dir.realpathAlloc(testing.allocator, ".");423 const actual_path = try dir.realPathFileAlloc(io, ".", gpa);
477 defer testing.allocator.free(actual_path);424 defer gpa.free(actual_path);
478425
479 try testing.expectEqualStrings(expected_path, actual_path);426 try expectEqualStrings(expected_path, actual_path);
480}427}
481428
482test "readLinkAbsolute" {429test "readLinkAbsolute" {
483 if (native_os == .wasi) return error.SkipZigTest;430 if (native_os == .wasi) return error.SkipZigTest;
484 if (native_os == .openbsd) return error.SkipZigTest;431 if (native_os == .openbsd) return error.SkipZigTest;
485432
433 const io = testing.io;
434
486 var tmp = tmpDir(.{});435 var tmp = tmpDir(.{});
487 defer tmp.cleanup();436 defer tmp.cleanup();
488437
489 // Create some targets438 // Create some targets
490 try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" });439 try tmp.dir.writeFile(io, .{ .sub_path = "file.txt", .data = "nonsense" });
491 try tmp.dir.makeDir("subdir");440 try tmp.dir.createDir(io, "subdir", .default_dir);
492441
493 // Get base abs path442 // Get base abs path
494 var arena = ArenaAllocator.init(testing.allocator);443 var arena_allocator = ArenaAllocator.init(testing.allocator);
495 defer arena.deinit();444 defer arena_allocator.deinit();
496 const allocator = arena.allocator();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
500 {449 {
501 const target_path = try fs.path.join(allocator, &.{ base_path, "file.txt" });450 const target_path = try Dir.path.join(arena, &.{ base_path, "file.txt" });
502 const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink1" });451 const symlink_path = try Dir.path.join(arena, &.{ base_path, "symlink1" });
503452
504 // Create symbolic link by path453 // Create symbolic link by path
505 try setupSymlinkAbsolute(target_path, symlink_path, .{});454 try setupSymlinkAbsolute(io, target_path, symlink_path, .{});
506 try testReadLinkAbsolute(target_path, symlink_path);455 try testReadLinkAbsolute(io, target_path, symlink_path);
507 }456 }
508 {457 {
509 const target_path = try fs.path.join(allocator, &.{ base_path, "subdir" });458 const target_path = try Dir.path.join(arena, &.{ base_path, "subdir" });
510 const symlink_path = try fs.path.join(allocator, &.{ base_path, "symlink2" });459 const symlink_path = try Dir.path.join(arena, &.{ base_path, "symlink2" });
511460
512 // Create symbolic link to a directory by path461 // Create symbolic link to a directory by path
513 try setupSymlinkAbsolute(target_path, symlink_path, .{ .is_directory = true });462 try setupSymlinkAbsolute(io, target_path, symlink_path, .{ .is_directory = true });
514 try testReadLinkAbsolute(target_path, symlink_path);463 try testReadLinkAbsolute(io, target_path, symlink_path);
515 }464 }
516}465}
517466
518test "Dir.Iterator" {467test "Dir.Iterator" {
468 const io = testing.io;
469
519 var tmp_dir = tmpDir(.{ .iterate = true });470 var tmp_dir = tmpDir(.{ .iterate = true });
520 defer tmp_dir.cleanup();471 defer tmp_dir.cleanup();
521472
522 // First, create a couple of entries to iterate over.473 // First, create a couple of entries to iterate over.
523 const file = try tmp_dir.dir.createFile("some_file", .{});474 const file = try tmp_dir.dir.createFile(io, "some_file", .{});
524 file.close();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
528 var arena = ArenaAllocator.init(testing.allocator);479 var arena = ArenaAllocator.init(testing.allocator);
529 defer arena.deinit();480 defer arena.deinit();
...@@ -533,19 +484,21 @@ test "Dir.Iterator" {...@@ -533,19 +484,21 @@ test "Dir.Iterator" {
533484
534 // Create iterator.485 // Create iterator.
535 var iter = tmp_dir.dir.iterate();486 var iter = tmp_dir.dir.iterate();
536 while (try iter.next()) |entry| {487 while (try iter.next(io)) |entry| {
537 // We cannot just store `entry` as on Windows, we're re-using the name buffer488 // We cannot just store `entry` as on Windows, we're re-using the name buffer
538 // which means we'll actually share the `name` pointer between entries!489 // which means we'll actually share the `name` pointer between entries!
539 const name = try allocator.dupe(u8, entry.name);490 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 });
541 }492 }
542493
543 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'494 try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
544 try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));495 try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 }));
545 try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));496 try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 }));
546}497}
547498
548test "Dir.Iterator many entries" {499test "Dir.Iterator many entries" {
500 const io = testing.io;
501
549 var tmp_dir = tmpDir(.{ .iterate = true });502 var tmp_dir = tmpDir(.{ .iterate = true });
550 defer tmp_dir.cleanup();503 defer tmp_dir.cleanup();
551504
...@@ -554,8 +507,8 @@ test "Dir.Iterator many entries" {...@@ -554,8 +507,8 @@ test "Dir.Iterator many entries" {
554 var buf: [4]u8 = undefined; // Enough to store "1024".507 var buf: [4]u8 = undefined; // Enough to store "1024".
555 while (i < num) : (i += 1) {508 while (i < num) : (i += 1) {
556 const name = try std.fmt.bufPrint(&buf, "{}", .{i});509 const name = try std.fmt.bufPrint(&buf, "{}", .{i});
557 const file = try tmp_dir.dir.createFile(name, .{});510 const file = try tmp_dir.dir.createFile(io, name, .{});
558 file.close();511 file.close(io);
559 }512 }
560513
561 var arena = ArenaAllocator.init(testing.allocator);514 var arena = ArenaAllocator.init(testing.allocator);
...@@ -566,29 +519,31 @@ test "Dir.Iterator many entries" {...@@ -566,29 +519,31 @@ test "Dir.Iterator many entries" {
566519
567 // Create iterator.520 // Create iterator.
568 var iter = tmp_dir.dir.iterate();521 var iter = tmp_dir.dir.iterate();
569 while (try iter.next()) |entry| {522 while (try iter.next(io)) |entry| {
570 // We cannot just store `entry` as on Windows, we're re-using the name buffer523 // We cannot just store `entry` as on Windows, we're re-using the name buffer
571 // which means we'll actually share the `name` pointer between entries!524 // which means we'll actually share the `name` pointer between entries!
572 const name = try allocator.dupe(u8, entry.name);525 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 });
574 }527 }
575528
576 i = 0;529 i = 0;
577 while (i < num) : (i += 1) {530 while (i < num) : (i += 1) {
578 const name = try std.fmt.bufPrint(&buf, "{}", .{i});531 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 }));
580 }533 }
581}534}
582535
583test "Dir.Iterator twice" {536test "Dir.Iterator twice" {
537 const io = testing.io;
538
584 var tmp_dir = tmpDir(.{ .iterate = true });539 var tmp_dir = tmpDir(.{ .iterate = true });
585 defer tmp_dir.cleanup();540 defer tmp_dir.cleanup();
586541
587 // First, create a couple of entries to iterate over.542 // First, create a couple of entries to iterate over.
588 const file = try tmp_dir.dir.createFile("some_file", .{});543 const file = try tmp_dir.dir.createFile(io, "some_file", .{});
589 file.close();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
593 var arena = ArenaAllocator.init(testing.allocator);548 var arena = ArenaAllocator.init(testing.allocator);
594 defer arena.deinit();549 defer arena.deinit();
...@@ -600,28 +555,30 @@ test "Dir.Iterator twice" {...@@ -600,28 +555,30 @@ test "Dir.Iterator twice" {
600555
601 // Create iterator.556 // Create iterator.
602 var iter = tmp_dir.dir.iterate();557 var iter = tmp_dir.dir.iterate();
603 while (try iter.next()) |entry| {558 while (try iter.next(io)) |entry| {
604 // We cannot just store `entry` as on Windows, we're re-using the name buffer559 // We cannot just store `entry` as on Windows, we're re-using the name buffer
605 // which means we'll actually share the `name` pointer between entries!560 // which means we'll actually share the `name` pointer between entries!
606 const name = try allocator.dupe(u8, entry.name);561 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 });
608 }563 }
609564
610 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'565 try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
611 try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));566 try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 }));
612 try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));567 try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 }));
613 }568 }
614}569}
615570
616test "Dir.Iterator reset" {571test "Dir.Iterator reset" {
572 const io = testing.io;
573
617 var tmp_dir = tmpDir(.{ .iterate = true });574 var tmp_dir = tmpDir(.{ .iterate = true });
618 defer tmp_dir.cleanup();575 defer tmp_dir.cleanup();
619576
620 // First, create a couple of entries to iterate over.577 // First, create a couple of entries to iterate over.
621 const file = try tmp_dir.dir.createFile("some_file", .{});578 const file = try tmp_dir.dir.createFile(io, "some_file", .{});
622 file.close();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
626 var arena = ArenaAllocator.init(testing.allocator);583 var arena = ArenaAllocator.init(testing.allocator);
627 defer arena.deinit();584 defer arena.deinit();
...@@ -634,48 +591,45 @@ test "Dir.Iterator reset" {...@@ -634,48 +591,45 @@ test "Dir.Iterator reset" {
634 while (i < 2) : (i += 1) {591 while (i < 2) : (i += 1) {
635 var entries = std.array_list.Managed(Dir.Entry).init(allocator);592 var entries = std.array_list.Managed(Dir.Entry).init(allocator);
636593
637 while (try iter.next()) |entry| {594 while (try iter.next(io)) |entry| {
638 // We cannot just store `entry` as on Windows, we're re-using the name buffer595 // We cannot just store `entry` as on Windows, we're re-using the name buffer
639 // which means we'll actually share the `name` pointer between entries!596 // which means we'll actually share the `name` pointer between entries!
640 const name = try allocator.dupe(u8, entry.name);597 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 });
642 }599 }
643600
644 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'601 try expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
645 try testing.expect(contains(&entries, .{ .name = "some_file", .kind = .file }));602 try expect(contains(&entries, .{ .name = "some_file", .kind = .file, .inode = 0 }));
646 try testing.expect(contains(&entries, .{ .name = "some_dir", .kind = .directory }));603 try expect(contains(&entries, .{ .name = "some_dir", .kind = .directory, .inode = 0 }));
647604
648 iter.reset();605 iter.reader.reset();
649 }606 }
650}607}
651608
652test "Dir.Iterator but dir is deleted during iteration" {609test "Dir.Iterator but dir is deleted during iteration" {
610 const io = testing.io;
611
653 var tmp = std.testing.tmpDir(.{});612 var tmp = std.testing.tmpDir(.{});
654 defer tmp.cleanup();613 defer tmp.cleanup();
655614
656 // Create directory and setup an iterator for it615 // Create directory and setup an iterator for it
657 var subdir = try tmp.dir.makeOpenPath("subdir", .{ .iterate = true });616 var subdir = try tmp.dir.createDirPathOpen(io, "subdir", .{ .open_options = .{ .iterate = true } });
658 defer subdir.close();617 defer subdir.close(io);
659618
660 var iterator = subdir.iterate();619 var iterator = subdir.iterate();
661620
662 // Create something to iterate over within the subdir621 // 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
665 // Then, before iterating, delete the directory that we're iterating.624 // Then, before iterating, delete the directory that we're iterating.
666 // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc.625 // This is a contrived reproduction, but this could happen outside of the program, in another thread, etc.
667 // If we get an error while trying to delete, we can skip this test (this will happen on platforms626 // If we get an error while trying to delete, we can skip this test (this will happen on platforms
668 // like Windows which will give FileBusy if the directory is currently open for iteration).627 // 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
671 // Now, when we try to iterate, the next call should return null immediately.630 // Now, when we try to iterate, the next call should return null immediately.
672 const entry = try iterator.next();631 const entry = try iterator.next(io);
673 try std.testing.expect(entry == null);632 try 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 }
679}633}
680634
681fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool {635fn 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...@@ -689,112 +643,122 @@ fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bo
689 return false;643 return false;
690}644}
691645
692test "Dir.realpath smoke test" {646test "Dir.realPath smoke test" {
693 if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest;647 if (native_os == .wasi) return error.SkipZigTest;
694648
695 try testWithAllSupportedPathTypes(struct {649 try testWithAllSupportedPathTypes(struct {
696 fn impl(ctx: *TestContext) !void {650 fn impl(ctx: *TestContext) !void {
697 const allocator = ctx.arena.allocator();651 const io = ctx.io;
652 const arena = ctx.arena.allocator();
698 const test_file_path = try ctx.transformPath("test_file");653 const test_file_path = try ctx.transformPath("test_file");
699 const test_dir_path = try ctx.transformPath("test_dir");654 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
702 // FileNotFound if the path doesn't exist657 // FileNotFound if the path doesn't exist
703 try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_file_path));658 try expectError(error.FileNotFound, ctx.dir.realPathFileAlloc(io, test_file_path, arena));
704 try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_file_path, &buf));659 try expectError(error.FileNotFound, ctx.dir.realPathFile(io, test_file_path, &buf));
705 try testing.expectError(error.FileNotFound, ctx.dir.realpathAlloc(allocator, test_dir_path));660 try expectError(error.FileNotFound, ctx.dir.realPathFileAlloc(io, test_dir_path, arena));
706 try testing.expectError(error.FileNotFound, ctx.dir.realpath(test_dir_path, &buf));661 try expectError(error.FileNotFound, ctx.dir.realPathFile(io, test_dir_path, &buf));
707662
708 // Now create the file and dir663 // Now create the file and dir
709 try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" });664 try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" });
710 try ctx.dir.makeDir(test_dir_path);665 try ctx.dir.createDir(io, test_dir_path, .default_dir);
711666
712 const base_path = try ctx.transformPath(".");667 const base_path = try ctx.transformPath(".");
713 const base_realpath = try ctx.dir.realpathAlloc(allocator, base_path);668 const base_realpath = try ctx.dir.realPathFileAlloc(io, base_path, arena);
714 const expected_file_path = try fs.path.join(669 const expected_file_path = try Dir.path.join(arena, &.{ base_realpath, "test_file" });
715 allocator,670 const expected_dir_path = try Dir.path.join(arena, &.{ base_realpath, "test_dir" });
716 &.{ base_realpath, "test_file" },
717 );
718 const expected_dir_path = try fs.path.join(
719 allocator,
720 &.{ base_realpath, "test_dir" },
721 );
722671
723 // First, test non-alloc version672 // First, test non-alloc version
724 {673 {
725 const file_path = try ctx.dir.realpath(test_file_path, &buf);674 const file_path = buf[0..try ctx.dir.realPathFile(io, test_file_path, &buf)];
726 try testing.expectEqualStrings(expected_file_path, file_path);675 try expectEqualStrings(expected_file_path, file_path);
727676
728 const dir_path = try ctx.dir.realpath(test_dir_path, &buf);677 const dir_path = buf[0..try ctx.dir.realPathFile(io, test_dir_path, &buf)];
729 try testing.expectEqualStrings(expected_dir_path, dir_path);678 try expectEqualStrings(expected_dir_path, dir_path);
730 }679 }
731680
732 // Next, test alloc version681 // Next, test alloc version
733 {682 {
734 const file_path = try ctx.dir.realpathAlloc(allocator, test_file_path);683 const file_path = try ctx.dir.realPathFileAlloc(io, test_file_path, arena);
735 try testing.expectEqualStrings(expected_file_path, file_path);684 try expectEqualStrings(expected_file_path, file_path);
736685
737 const dir_path = try ctx.dir.realpathAlloc(allocator, test_dir_path);686 const dir_path = try ctx.dir.realPathFileAlloc(io, test_dir_path, arena);
738 try testing.expectEqualStrings(expected_dir_path, dir_path);687 try expectEqualStrings(expected_dir_path, dir_path);
739 }688 }
740 }689 }
741 }.impl);690 }.impl);
742}691}
743692
744test "readFileAlloc" {693test "readFileAlloc" {
694 const io = testing.io;
695
745 var tmp_dir = tmpDir(.{});696 var tmp_dir = tmpDir(.{});
746 defer tmp_dir.cleanup();697 defer tmp_dir.cleanup();
747698
748 var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });699 var file = try tmp_dir.dir.createFile(io, "test_file", .{ .read = true });
749 defer file.close();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));
752 defer testing.allocator.free(buf1);703 defer testing.allocator.free(buf1);
753 try testing.expectEqualStrings("", buf1);704 try expectEqualStrings("", buf1);
754705
755 const write_buf: []const u8 = "this is a test.\nthis is a test.\nthis is a test.\nthis is a test.\n";706 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
758 {709 {
759 // max_bytes > file_size710 // 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));
761 defer testing.allocator.free(buf2);712 defer testing.allocator.free(buf2);
762 try testing.expectEqualStrings(write_buf, buf2);713 try expectEqualStrings(write_buf, buf2);
763 }714 }
764715
765 {716 {
766 // max_bytes == file_size717 // max_bytes == file_size
767 try testing.expectError(718 try expectError(
768 error.StreamTooLong,719 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)),
770 );721 );
771 }722 }
772723
773 {724 {
774 // max_bytes == file_size + 1725 // 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));
776 defer testing.allocator.free(buf2);727 defer testing.allocator.free(buf2);
777 try testing.expectEqualStrings(write_buf, buf2);728 try expectEqualStrings(write_buf, buf2);
778 }729 }
779730
780 // max_bytes < file_size731 // max_bytes < file_size
781 try testing.expectError(732 try expectError(
782 error.StreamTooLong,733 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)),
784 );735 );
785}736}
786737
787test "Dir.statFile" {738test "Dir.statFile" {
788 try testWithAllSupportedPathTypes(struct {739 try testWithAllSupportedPathTypes(struct {
789 fn impl(ctx: *TestContext) !void {740 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);749 const stat = try ctx.dir.statFile(io, test_file_name, .{});
797 try testing.expectEqual(File.Kind.file, stat.kind);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 }
798 }762 }
799 }.impl);763 }.impl);
800}764}
...@@ -802,12 +766,13 @@ test "Dir.statFile" {...@@ -802,12 +766,13 @@ test "Dir.statFile" {
802test "statFile on dangling symlink" {766test "statFile on dangling symlink" {
803 try testWithAllSupportedPathTypes(struct {767 try testWithAllSupportedPathTypes(struct {
804 fn impl(ctx: *TestContext) !void {768 fn impl(ctx: *TestContext) !void {
769 const io = ctx.io;
805 const symlink_name = try ctx.transformPath("dangling-symlink");770 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, .{}));
811 }776 }
812 }.impl);777 }.impl);
813}778}
...@@ -815,25 +780,27 @@ test "statFile on dangling symlink" {...@@ -815,25 +780,27 @@ test "statFile on dangling symlink" {
815test "directory operations on files" {780test "directory operations on files" {
816 try testWithAllSupportedPathTypes(struct {781 try testWithAllSupportedPathTypes(struct {
817 fn impl(ctx: *TestContext) !void {782 fn impl(ctx: *TestContext) !void {
783 const io = ctx.io;
784
818 const test_file_name = try ctx.transformPath("test_file");785 const test_file_name = try ctx.transformPath("test_file");
819786
820 var file = try ctx.dir.createFile(test_file_name, .{ .read = true });787 var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true });
821 file.close();788 file.close(io);
822789
823 try testing.expectError(error.PathAlreadyExists, ctx.dir.makeDir(test_file_name));790 try expectError(error.PathAlreadyExists, ctx.dir.createDir(io, test_file_name, .default_dir));
824 try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{}));791 try expectError(error.NotDir, ctx.dir.openDir(io, test_file_name, .{}));
825 try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name));792 try expectError(error.NotDir, ctx.dir.deleteDir(io, test_file_name));
826793
827 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {794 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
828 try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name));795 try expectError(error.PathAlreadyExists, Dir.createDirAbsolute(io, test_file_name, .default_dir));
829 try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name));796 try expectError(error.NotDir, Dir.deleteDirAbsolute(io, test_file_name));
830 }797 }
831798
832 // ensure the file still exists and is a file as a sanity check799 // ensure the file still exists and is a file as a sanity check
833 file = try ctx.dir.openFile(test_file_name, .{});800 file = try ctx.dir.openFile(io, test_file_name, .{});
834 const stat = try file.stat();801 const stat = try file.stat(io);
835 try testing.expectEqual(File.Kind.file, stat.kind);802 try expectEqual(File.Kind.file, stat.kind);
836 file.close();803 file.close(io);
837 }804 }
838 }.impl);805 }.impl);
839}806}
...@@ -842,81 +809,91 @@ test "file operations on directories" {...@@ -842,81 +809,91 @@ test "file operations on directories" {
842 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759809 // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759
843 if (native_os == .freebsd) return error.SkipZigTest;810 if (native_os == .freebsd) return error.SkipZigTest;
844811
812 const io = testing.io;
813
845 try testWithAllSupportedPathTypes(struct {814 try testWithAllSupportedPathTypes(struct {
846 fn impl(ctx: *TestContext) !void {815 fn impl(ctx: *TestContext) !void {
847 const test_dir_name = try ctx.transformPath("test_dir");816 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, .{}));820 try expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{}));
852 try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name));821 try expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name));
853 switch (native_os) {822 switch (native_os) {
854 .dragonfly, .netbsd => {823 .dragonfly, .netbsd => {
855 // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732824 // 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);
857 testing.allocator.free(buf);826 testing.allocator.free(buf);
858 },827 },
859 .wasi => {828 .wasi => {
860 // WASI return EBADF, which gets mapped to NotOpenForReading.829 // WASI return EBADF, which gets mapped to NotOpenForReading.
861 // See https://github.com/bytecodealliance/wasmtime/issues/1935830 // 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));
863 },832 },
864 else => {833 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));
866 },835 },
867 }836 }
868837
869 if (native_os == .wasi and builtin.link_libc) {838 if (native_os == .wasi and builtin.link_libc) {
870 // wasmtime unexpectedly succeeds here, see https://github.com/ziglang/zig/issues/20747839 // 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 });840 const handle = try ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write });
872 handle.close();841 handle.close(io);
873 } else {842 } else {
874 // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms.843 // 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/5732844 try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .mode = .read_write }));
876 try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }));
877 }845 }
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
879 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {853 if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) {
880 try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{}));854 try expectError(error.IsDir, Dir.createFileAbsolute(io, test_dir_name, .{}));
881 try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name));855 try expectError(error.IsDir, Dir.deleteFileAbsolute(io, test_dir_name));
882 }856 }
883857
884 // ensure the directory still exists as a sanity check858 // ensure the directory still exists as a sanity check
885 var dir = try ctx.dir.openDir(test_dir_name, .{});859 var dir = try ctx.dir.openDir(io, test_dir_name, .{});
886 dir.close();860 dir.close(io);
887 }861 }
888 }.impl);862 }.impl);
889}863}
890864
891test "makeOpenPath parent dirs do not exist" {865test "createDirPathOpen parent dirs do not exist" {
866 const io = testing.io;
867
892 var tmp_dir = tmpDir(.{});868 var tmp_dir = tmpDir(.{});
893 defer tmp_dir.cleanup();869 defer tmp_dir.cleanup();
894870
895 var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{});871 var dir = try tmp_dir.dir.createDirPathOpen(io, "root_dir/parent_dir/some_dir", .{});
896 dir.close();872 dir.close(io);
897873
898 // double check that the full directory structure was created874 // double check that the full directory structure was created
899 var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{});875 var dir_verification = try tmp_dir.dir.openDir(io, "root_dir/parent_dir/some_dir", .{});
900 dir_verification.close();876 dir_verification.close(io);
901}877}
902878
903test "deleteDir" {879test "deleteDir" {
904 try testWithAllSupportedPathTypes(struct {880 try testWithAllSupportedPathTypes(struct {
905 fn impl(ctx: *TestContext) !void {881 fn impl(ctx: *TestContext) !void {
882 const io = ctx.io;
906 const test_dir_path = try ctx.transformPath("test_dir");883 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
909 // deleting a non-existent directory886 // 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
912 // deleting a non-empty directory889 // deleting a non-empty directory
913 try ctx.dir.makeDir(test_dir_path);890 try ctx.dir.createDir(io, test_dir_path, .default_dir);
914 try ctx.dir.writeFile(.{ .sub_path = test_file_path, .data = "" });891 try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" });
915 try testing.expectError(error.DirNotEmpty, ctx.dir.deleteDir(test_dir_path));892 try expectError(error.DirNotEmpty, ctx.dir.deleteDir(io, test_dir_path));
916893
917 // deleting an empty directory894 // deleting an empty directory
918 try ctx.dir.deleteFile(test_file_path);895 try ctx.dir.deleteFile(io, test_file_path);
919 try ctx.dir.deleteDir(test_dir_path);896 try ctx.dir.deleteDir(io, test_dir_path);
920 }897 }
921 }.impl);898 }.impl);
922}899}
...@@ -924,6 +901,7 @@ test "deleteDir" {...@@ -924,6 +901,7 @@ test "deleteDir" {
924test "Dir.rename files" {901test "Dir.rename files" {
925 try testWithAllSupportedPathTypes(struct {902 try testWithAllSupportedPathTypes(struct {
926 fn impl(ctx: *TestContext) !void {903 fn impl(ctx: *TestContext) !void {
904 const io = ctx.io;
927 // Rename on Windows can hit intermittent AccessDenied errors905 // Rename on Windows can hit intermittent AccessDenied errors
928 // when certain conditions are true about the host system.906 // when certain conditions are true about the host system.
929 // For now, skip this test when the path type is UNC to avoid them.907 // For now, skip this test when the path type is UNC to avoid them.
...@@ -933,32 +911,32 @@ test "Dir.rename files" {...@@ -933,32 +911,32 @@ test "Dir.rename files" {
933 const missing_file_path = try ctx.transformPath("missing_file_name");911 const missing_file_path = try ctx.transformPath("missing_file_name");
934 const something_else_path = try ctx.transformPath("something_else");912 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
938 // Renaming files916 // Renaming files
939 const test_file_name = try ctx.transformPath("test_file");917 const test_file_name = try ctx.transformPath("test_file");
940 const renamed_test_file_name = try ctx.transformPath("test_file_renamed");918 const renamed_test_file_name = try ctx.transformPath("test_file_renamed");
941 var file = try ctx.dir.createFile(test_file_name, .{ .read = true });919 var file = try ctx.dir.createFile(io, test_file_name, .{ .read = true });
942 file.close();920 file.close(io);
943 try ctx.dir.rename(test_file_name, renamed_test_file_name);921 try ctx.dir.rename(test_file_name, ctx.dir, renamed_test_file_name, io);
944922
945 // Ensure the file was renamed923 // Ensure the file was renamed
946 try testing.expectError(error.FileNotFound, ctx.dir.openFile(test_file_name, .{}));924 try expectError(error.FileNotFound, ctx.dir.openFile(io, test_file_name, .{}));
947 file = try ctx.dir.openFile(renamed_test_file_name, .{});925 file = try ctx.dir.openFile(io, renamed_test_file_name, .{});
948 file.close();926 file.close(io);
949927
950 // Rename to self succeeds928 // 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
953 // Rename to existing file succeeds931 // Rename to existing file succeeds
954 const existing_file_path = try ctx.transformPath("existing_file");932 const existing_file_path = try ctx.transformPath("existing_file");
955 var existing_file = try ctx.dir.createFile(existing_file_path, .{ .read = true });933 var existing_file = try ctx.dir.createFile(io, existing_file_path, .{ .read = true });
956 existing_file.close();934 existing_file.close(io);
957 try ctx.dir.rename(renamed_test_file_name, existing_file_path);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, .{}));937 try expectError(error.FileNotFound, ctx.dir.openFile(io, renamed_test_file_name, .{}));
960 file = try ctx.dir.openFile(existing_file_path, .{});938 file = try ctx.dir.openFile(io, existing_file_path, .{});
961 file.close();939 file.close(io);
962 }940 }
963 }.impl);941 }.impl);
964}942}
...@@ -966,6 +944,8 @@ test "Dir.rename files" {...@@ -966,6 +944,8 @@ test "Dir.rename files" {
966test "Dir.rename directories" {944test "Dir.rename directories" {
967 try testWithAllSupportedPathTypes(struct {945 try testWithAllSupportedPathTypes(struct {
968 fn impl(ctx: *TestContext) !void {946 fn impl(ctx: *TestContext) !void {
947 const io = ctx.io;
948
969 // Rename on Windows can hit intermittent AccessDenied errors949 // Rename on Windows can hit intermittent AccessDenied errors
970 // when certain conditions are true about the host system.950 // when certain conditions are true about the host system.
971 // For now, skip this test when the path type is UNC to avoid them.951 // For now, skip this test when the path type is UNC to avoid them.
...@@ -976,27 +956,27 @@ test "Dir.rename directories" {...@@ -976,27 +956,27 @@ test "Dir.rename directories" {
976 const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed");956 const test_dir_renamed_path = try ctx.transformPath("test_dir_renamed");
977957
978 // Renaming directories958 // Renaming directories
979 try ctx.dir.makeDir(test_dir_path);959 try ctx.dir.createDir(io, test_dir_path, .default_dir);
980 try ctx.dir.rename(test_dir_path, test_dir_renamed_path);960 try ctx.dir.rename(test_dir_path, ctx.dir, test_dir_renamed_path, io);
981961
982 // Ensure the directory was renamed962 // Ensure the directory was renamed
983 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));963 try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{}));
984 var dir = try ctx.dir.openDir(test_dir_renamed_path, .{});964 var dir = try ctx.dir.openDir(io, test_dir_renamed_path, .{});
985965
986 // Put a file in the directory966 // Put a file in the directory
987 var file = try dir.createFile("test_file", .{ .read = true });967 var file = try dir.createFile(io, "test_file", .{ .read = true });
988 file.close();968 file.close(io);
989 dir.close();969 dir.close(io);
990970
991 const test_dir_renamed_again_path = try ctx.transformPath("test_dir_renamed_again");971 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
994 // Ensure the directory was renamed and the file still exists in it974 // 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, .{}));975 try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_renamed_path, .{}));
996 dir = try ctx.dir.openDir(test_dir_renamed_again_path, .{});976 dir = try ctx.dir.openDir(io, test_dir_renamed_again_path, .{});
997 file = try dir.openFile("test_file", .{});977 file = try dir.openFile(io, "test_file", .{});
998 file.close();978 file.close(io);
999 dir.close();979 dir.close(io);
1000 }980 }
1001 }.impl);981 }.impl);
1002}982}
...@@ -1007,17 +987,19 @@ test "Dir.rename directory onto empty dir" {...@@ -1007,17 +987,19 @@ test "Dir.rename directory onto empty dir" {
1007987
1008 try testWithAllSupportedPathTypes(struct {988 try testWithAllSupportedPathTypes(struct {
1009 fn impl(ctx: *TestContext) !void {989 fn impl(ctx: *TestContext) !void {
990 const io = ctx.io;
991
1010 const test_dir_path = try ctx.transformPath("test_dir");992 const test_dir_path = try ctx.transformPath("test_dir");
1011 const target_dir_path = try ctx.transformPath("target_dir_path");993 const target_dir_path = try ctx.transformPath("target_dir_path");
1012994
1013 try ctx.dir.makeDir(test_dir_path);995 try ctx.dir.createDir(io, test_dir_path, .default_dir);
1014 try ctx.dir.makeDir(target_dir_path);996 try ctx.dir.createDir(io, target_dir_path, .default_dir);
1015 try ctx.dir.rename(test_dir_path, target_dir_path);997 try ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io);
1016998
1017 // Ensure the directory was renamed999 // Ensure the directory was renamed
1018 try testing.expectError(error.FileNotFound, ctx.dir.openDir(test_dir_path, .{}));1000 try expectError(error.FileNotFound, ctx.dir.openDir(io, test_dir_path, .{}));
1019 var dir = try ctx.dir.openDir(target_dir_path, .{});1001 var dir = try ctx.dir.openDir(io, target_dir_path, .{});
1020 dir.close();1002 dir.close(io);
1021 }1003 }
1022 }.impl);1004 }.impl);
1023}1005}
...@@ -1028,22 +1010,23 @@ test "Dir.rename directory onto non-empty dir" {...@@ -1028,22 +1010,23 @@ test "Dir.rename directory onto non-empty dir" {
10281010
1029 try testWithAllSupportedPathTypes(struct {1011 try testWithAllSupportedPathTypes(struct {
1030 fn impl(ctx: *TestContext) !void {1012 fn impl(ctx: *TestContext) !void {
1013 const io = ctx.io;
1031 const test_dir_path = try ctx.transformPath("test_dir");1014 const test_dir_path = try ctx.transformPath("test_dir");
1032 const target_dir_path = try ctx.transformPath("target_dir_path");1015 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, .{});1019 var target_dir = try ctx.dir.createDirPathOpen(io, target_dir_path, .{});
1037 var file = try target_dir.createFile("test_file", .{ .read = true });1020 var file = try target_dir.createFile(io, "test_file", .{ .read = true });
1038 file.close();1021 file.close(io);
1039 target_dir.close();1022 target_dir.close(io);
10401023
1041 // Rename should fail with PathAlreadyExists if target_dir is non-empty1024 // 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
1044 // Ensure the directory was not renamed1027 // Ensure the directory was not renamed
1045 var dir = try ctx.dir.openDir(test_dir_path, .{});1028 var dir = try ctx.dir.openDir(io, test_dir_path, .{});
1046 dir.close();1029 dir.close(io);
1047 }1030 }
1048 }.impl);1031 }.impl);
1049}1032}
...@@ -1054,19 +1037,22 @@ test "Dir.rename file <-> dir" {...@@ -1054,19 +1037,22 @@ test "Dir.rename file <-> dir" {
10541037
1055 try testWithAllSupportedPathTypes(struct {1038 try testWithAllSupportedPathTypes(struct {
1056 fn impl(ctx: *TestContext) !void {1039 fn impl(ctx: *TestContext) !void {
1040 const io = ctx.io;
1057 const test_file_path = try ctx.transformPath("test_file");1041 const test_file_path = try ctx.transformPath("test_file");
1058 const test_dir_path = try ctx.transformPath("test_dir");1042 const test_dir_path = try ctx.transformPath("test_dir");
10591043
1060 var file = try ctx.dir.createFile(test_file_path, .{ .read = true });1044 var file = try ctx.dir.createFile(io, test_file_path, .{ .read = true });
1061 file.close();1045 file.close(io);
1062 try ctx.dir.makeDir(test_dir_path);1046 try ctx.dir.createDir(io, test_dir_path, .default_dir);
1063 try testing.expectError(error.IsDir, ctx.dir.rename(test_file_path, test_dir_path));1047 try expectError(error.IsDir, ctx.dir.rename(test_file_path, ctx.dir, test_dir_path, io));
1064 try testing.expectError(error.NotDir, ctx.dir.rename(test_dir_path, test_file_path));1048 try expectError(error.NotDir, ctx.dir.rename(test_dir_path, ctx.dir, test_file_path, io));
1065 }1049 }
1066 }.impl);1050 }.impl);
1067}1051}
10681052
1069test "rename" {1053test "rename" {
1054 const io = testing.io;
1055
1070 var tmp_dir1 = tmpDir(.{});1056 var tmp_dir1 = tmpDir(.{});
1071 defer tmp_dir1.cleanup();1057 defer tmp_dir1.cleanup();
10721058
...@@ -1076,20 +1062,22 @@ test "rename" {...@@ -1076,20 +1062,22 @@ test "rename" {
1076 // Renaming files1062 // Renaming files
1077 const test_file_name = "test_file";1063 const test_file_name = "test_file";
1078 const renamed_test_file_name = "test_file_renamed";1064 const renamed_test_file_name = "test_file_renamed";
1079 var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true });1065 var file = try tmp_dir1.dir.createFile(io, test_file_name, .{ .read = true });
1080 file.close();1066 file.close(io);
1081 try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name);1067 try Dir.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name, io);
10821068
1083 // ensure the file was renamed1069 // ensure the file was renamed
1084 try testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{}));1070 try expectError(error.FileNotFound, tmp_dir1.dir.openFile(io, test_file_name, .{}));
1085 file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{});1071 file = try tmp_dir2.dir.openFile(io, renamed_test_file_name, .{});
1086 file.close();1072 file.close(io);
1087}1073}
10881074
1089test "renameAbsolute" {1075test "renameAbsolute" {
1090 if (native_os == .wasi) return error.SkipZigTest;1076 if (native_os == .wasi) return error.SkipZigTest;
1091 if (native_os == .openbsd) return error.SkipZigTest;1077 if (native_os == .openbsd) return error.SkipZigTest;
10921078
1079 const io = testing.io;
1080
1093 var tmp_dir = tmpDir(.{});1081 var tmp_dir = tmpDir(.{});
1094 defer tmp_dir.cleanup();1082 defer tmp_dir.cleanup();
10951083
...@@ -1098,289 +1086,359 @@ test "renameAbsolute" {...@@ -1098,289 +1086,359 @@ test "renameAbsolute" {
1098 defer arena.deinit();1086 defer arena.deinit();
1099 const allocator = arena.allocator();1087 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(1091 try expectError(error.FileNotFound, Dir.renameAbsolute(
1104 try fs.path.join(allocator, &.{ base_path, "missing_file_name" }),1092 try Dir.path.join(allocator, &.{ base_path, "missing_file_name" }),
1105 try fs.path.join(allocator, &.{ base_path, "something_else" }),1093 try Dir.path.join(allocator, &.{ base_path, "something_else" }),
1094 io,
1106 ));1095 ));
11071096
1108 // Renaming files1097 // Renaming files
1109 const test_file_name = "test_file";1098 const test_file_name = "test_file";
1110 const renamed_test_file_name = "test_file_renamed";1099 const renamed_test_file_name = "test_file_renamed";
1111 var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });1100 var file = try tmp_dir.dir.createFile(io, test_file_name, .{ .read = true });
1112 file.close();1101 file.close(io);
1113 try fs.renameAbsolute(1102 try Dir.renameAbsolute(
1114 try fs.path.join(allocator, &.{ base_path, test_file_name }),1103 try Dir.path.join(allocator, &.{ base_path, test_file_name }),
1115 try fs.path.join(allocator, &.{ base_path, renamed_test_file_name }),1104 try Dir.path.join(allocator, &.{ base_path, renamed_test_file_name }),
1105 io,
1116 );1106 );
11171107
1118 // ensure the file was renamed1108 // ensure the file was renamed
1119 try testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));1109 try expectError(error.FileNotFound, tmp_dir.dir.openFile(io, test_file_name, .{}));
1120 file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});1110 file = try tmp_dir.dir.openFile(io, renamed_test_file_name, .{});
1121 const stat = try file.stat();1111 const stat = try file.stat(io);
1122 try testing.expectEqual(File.Kind.file, stat.kind);1112 try expectEqual(File.Kind.file, stat.kind);
1123 file.close();1113 file.close(io);
11241114
1125 // Renaming directories1115 // Renaming directories
1126 const test_dir_name = "test_dir";1116 const test_dir_name = "test_dir";
1127 const renamed_test_dir_name = "test_dir_renamed";1117 const renamed_test_dir_name = "test_dir_renamed";
1128 try tmp_dir.dir.makeDir(test_dir_name);1118 try tmp_dir.dir.createDir(io, test_dir_name, .default_dir);
1129 try fs.renameAbsolute(1119 try Dir.renameAbsolute(
1130 try fs.path.join(allocator, &.{ base_path, test_dir_name }),1120 try Dir.path.join(allocator, &.{ base_path, test_dir_name }),
1131 try fs.path.join(allocator, &.{ base_path, renamed_test_dir_name }),1121 try Dir.path.join(allocator, &.{ base_path, renamed_test_dir_name }),
1122 io,
1132 );1123 );
11331124
1134 // ensure the directory was renamed1125 // ensure the directory was renamed
1135 try testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{}));1126 try expectError(error.FileNotFound, tmp_dir.dir.openDir(io, test_dir_name, .{}));
1136 var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{});1127 var dir = try tmp_dir.dir.openDir(io, renamed_test_dir_name, .{});
1137 dir.close();1128 dir.close(io);
1138}1129}
11391130
1140test "openSelfExe" {1131test "openExecutable" {
1141 if (native_os == .wasi) return error.SkipZigTest;1132 if (native_os == .wasi) return error.SkipZigTest;
11421133
1143 const self_exe_file = try std.fs.openSelfExe(.{});1134 const io = testing.io;
1144 self_exe_file.close();1135
1136 const self_exe_file = try std.process.openExecutable(io, .{});
1137 self_exe_file.close(io);
1145}1138}
11461139
1147test "selfExePath" {1140test "executablePath" {
1148 if (native_os == .wasi) return error.SkipZigTest;1141 if (native_os == .wasi) return error.SkipZigTest;
11491142
1150 var buf: [fs.max_path_bytes]u8 = undefined;1143 const io = testing.io;
1151 const buf_self_exe_path = try std.fs.selfExePath(&buf);1144 var buf: [Dir.max_path_bytes]u8 = undefined;
1152 const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator);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);
1153 defer testing.allocator.free(alloc_self_exe_path);1148 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);
1155}1150}
11561151
1157test "deleteTree does not follow symlinks" {1152test "deleteTree does not follow symlinks" {
1153 const io = testing.io;
1154
1158 var tmp = tmpDir(.{});1155 var tmp = tmpDir(.{});
1159 defer tmp.cleanup();1156 defer tmp.cleanup();
11601157
1161 try tmp.dir.makePath("b");1158 try tmp.dir.createDirPath(io, "b");
1162 {1159 {
1163 var a = try tmp.dir.makeOpenPath("a", .{});1160 var a = try tmp.dir.createDirPathOpen(io, "a", .{});
1164 defer a.close();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 });
1167 }1164 }
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", .{}));1168 try expectError(error.FileNotFound, tmp.dir.access(io, "a", .{}));
1172 try tmp.dir.access("b", .{});1169 try tmp.dir.access(io, "b", .{});
1173}1170}
11741171
1175test "deleteTree on a symlink" {1172test "deleteTree on a symlink" {
1173 const io = testing.io;
1174
1176 var tmp = tmpDir(.{});1175 var tmp = tmpDir(.{});
1177 defer tmp.cleanup();1176 defer tmp.cleanup();
11781177
1179 // Symlink to a file1178 // Symlink to a file
1180 try tmp.dir.writeFile(.{ .sub_path = "file", .data = "" });1179 try tmp.dir.writeFile(io, .{ .sub_path = "file", .data = "" });
1181 try setupSymlink(tmp.dir, "file", "filelink", .{});1180 try setupSymlink(io, tmp.dir, "file", "filelink", .{});
11821181
1183 try tmp.dir.deleteTree("filelink");1182 try tmp.dir.deleteTree(io, "filelink");
1184 try testing.expectError(error.FileNotFound, tmp.dir.access("filelink", .{}));1183 try expectError(error.FileNotFound, tmp.dir.access(io, "filelink", .{}));
1185 try tmp.dir.access("file", .{});1184 try tmp.dir.access(io, "file", .{});
11861185
1187 // Symlink to a directory1186 // Symlink to a directory
1188 try tmp.dir.makePath("dir");1187 try tmp.dir.createDirPath(io, "dir");
1189 try setupSymlink(tmp.dir, "dir", "dirlink", .{ .is_directory = true });1188 try setupSymlink(io, tmp.dir, "dir", "dirlink", .{ .is_directory = true });
11901189
1191 try tmp.dir.deleteTree("dirlink");1190 try tmp.dir.deleteTree(io, "dirlink");
1192 try testing.expectError(error.FileNotFound, tmp.dir.access("dirlink", .{}));1191 try expectError(error.FileNotFound, tmp.dir.access(io, "dirlink", .{}));
1193 try tmp.dir.access("dir", .{});1192 try tmp.dir.access(io, "dir", .{});
1194}1193}
11951194
1196test "makePath, put some files in it, deleteTree" {1195test "createDirPath, put some files in it, deleteTree" {
1197 try testWithAllSupportedPathTypes(struct {1196 try testWithAllSupportedPathTypes(struct {
1198 fn impl(ctx: *TestContext) !void {1197 fn impl(ctx: *TestContext) !void {
1198 const io = ctx.io;
1199 const allocator = ctx.arena.allocator();1199 const allocator = ctx.arena.allocator();
1200 const dir_path = try ctx.transformPath("os_test_tmp");1200 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" }));1202 try ctx.dir.createDirPath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
1203 try ctx.dir.writeFile(.{1203 try ctx.dir.writeFile(io, .{
1204 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),1204 .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),
1205 .data = "nonsense",1205 .data = "nonsense",
1206 });1206 });
1207 try ctx.dir.writeFile(.{1207 try ctx.dir.writeFile(io, .{
1208 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),1208 .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),
1209 .data = "blah",1209 .data = "blah",
1210 });1210 });
12111211
1212 try ctx.dir.deleteTree(dir_path);1212 try ctx.dir.deleteTree(io, dir_path);
1213 try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));1213 try expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{}));
1214 }1214 }
1215 }.impl);1215 }.impl);
1216}1216}
12171217
1218test "makePath, put some files in it, deleteTreeMinStackSize" {1218test "createDirPath, put some files in it, deleteTreeMinStackSize" {
1219 try testWithAllSupportedPathTypes(struct {1219 try testWithAllSupportedPathTypes(struct {
1220 fn impl(ctx: *TestContext) !void {1220 fn impl(ctx: *TestContext) !void {
1221 const io = ctx.io;
1221 const allocator = ctx.arena.allocator();1222 const allocator = ctx.arena.allocator();
1222 const dir_path = try ctx.transformPath("os_test_tmp");1223 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.createDirPath(io, try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c" }));
1225 try ctx.dir.writeFile(.{1226 try ctx.dir.writeFile(io, .{
1226 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),1227 .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "c", "file.txt" }),
1227 .data = "nonsense",1228 .data = "nonsense",
1228 });1229 });
1229 try ctx.dir.writeFile(.{1230 try ctx.dir.writeFile(io, .{
1230 .sub_path = try fs.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),1231 .sub_path = try Dir.path.join(allocator, &.{ "os_test_tmp", "b", "file2.txt" }),
1231 .data = "blah",1232 .data = "blah",
1232 });1233 });
12331234
1234 try ctx.dir.deleteTreeMinStackSize(dir_path);1235 try ctx.dir.deleteTreeMinStackSize(io, dir_path);
1235 try testing.expectError(error.FileNotFound, ctx.dir.openDir(dir_path, .{}));1236 try expectError(error.FileNotFound, ctx.dir.openDir(io, dir_path, .{}));
1236 }1237 }
1237 }.impl);1238 }.impl);
1238}1239}
12391240
1240test "makePath in a directory that no longer exists" {1241test "createDirPath in a directory that no longer exists" {
1241 if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir1242 if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir
12421243
1244 const io = testing.io;
1245
1243 var tmp = tmpDir(.{});1246 var tmp = tmpDir(.{});
1244 defer tmp.cleanup();1247 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"));
1248}1251}
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
1251 var tmp = tmpDir(.{});1256 var tmp = tmpDir(.{});
1252 defer tmp.cleanup();1257 defer tmp.cleanup();
12531258
1254 try tmp.dir.makeDir("foo");1259 try tmp.dir.createDir(io, "foo", .default_dir);
1255 try tmp.dir.writeFile(.{ .sub_path = "foo/bar", .data = "" });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"));
1258}1263}
12591264
1260fn expectDir(dir: Dir, path: []const u8) !void {1265fn expectDir(io: Io, dir: Dir, path: []const u8) !void {
1261 var d = try dir.openDir(path, .{});1266 var d = try dir.openDir(io, path, .{});
1262 d.close();1267 d.close(io);
1263}1268}
12641269
1265test "makepath existing directories" {1270test "makepath existing directories" {
1271 const io = testing.io;
1272
1266 var tmp = tmpDir(.{});1273 var tmp = tmpDir(.{});
1267 defer tmp.cleanup();1274 defer tmp.cleanup();
12681275
1269 try tmp.dir.makeDir("A");1276 try tmp.dir.createDir(io, "A", .default_dir);
1270 var tmpA = try tmp.dir.openDir("A", .{});1277 var tmpA = try tmp.dir.openDir(io, "A", .{});
1271 defer tmpA.close();1278 defer tmpA.close(io);
1272 try tmpA.makeDir("B");1279 try tmpA.createDir(io, "B", .default_dir);
12731280
1274 const testPath = "A" ++ fs.path.sep_str ++ "B" ++ fs.path.sep_str ++ "C";1281 const testPath = "A" ++ Dir.path.sep_str ++ "B" ++ Dir.path.sep_str ++ "C";
1275 try tmp.dir.makePath(testPath);1282 try tmp.dir.createDirPath(io, testPath);
12761283
1277 try expectDir(tmp.dir, testPath);1284 try expectDir(io, tmp.dir, testPath);
1278}1285}
12791286
1280test "makepath through existing valid symlink" {1287test "makepath through existing valid symlink" {
1288 const io = testing.io;
1289
1281 var tmp = tmpDir(.{});1290 var tmp = tmpDir(.{});
1282 defer tmp.cleanup();1291 defer tmp.cleanup();
12831292
1284 try tmp.dir.makeDir("realfolder");1293 try tmp.dir.createDir(io, "realfolder", .default_dir);
1285 try setupSymlink(tmp.dir, "." ++ fs.path.sep_str ++ "realfolder", "working-symlink", .{});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");
1290}1299}
12911300
1292test "makepath relative walks" {1301test "makepath relative walks" {
1302 const io = testing.io;
1303
1293 var tmp = tmpDir(.{});1304 var tmp = tmpDir(.{});
1294 defer tmp.cleanup();1305 defer tmp.cleanup();
12951306
1296 const relPath = try fs.path.join(testing.allocator, &.{1307 const relPath = try Dir.path.join(testing.allocator, &.{
1297 "first", "..", "second", "..", "third", "..", "first", "A", "..", "B", "..", "C",1308 "first", "..", "second", "..", "third", "..", "first", "A", "..", "B", "..", "C",
1298 });1309 });
1299 defer testing.allocator.free(relPath);1310 defer testing.allocator.free(relPath);
13001311
1301 try tmp.dir.makePath(relPath);1312 try tmp.dir.createDirPath(io, relPath);
13021313
1303 // How .. is handled is different on Windows than non-Windows1314 // How .. is handled is different on Windows than non-Windows
1304 switch (native_os) {1315 switch (native_os) {
1305 .windows => {1316 .windows => {
1306 // On Windows, .. is resolved before passing the path to NtCreateFile,1317 // On Windows, .. is resolved before passing the path to NtCreateFile,
1307 // meaning everything except `first/C` drops out.1318 // meaning everything except `first/C` drops out.
1308 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C");1319 try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "C");
1309 try testing.expectError(error.FileNotFound, tmp.dir.access("second", .{}));1320 try expectError(error.FileNotFound, tmp.dir.access(io, "second", .{}));
1310 try testing.expectError(error.FileNotFound, tmp.dir.access("third", .{}));1321 try expectError(error.FileNotFound, tmp.dir.access(io, "third", .{}));
1311 },1322 },
1312 else => {1323 else => {
1313 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "A");1324 try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "A");
1314 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "B");1325 try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "B");
1315 try expectDir(tmp.dir, "first" ++ fs.path.sep_str ++ "C");1326 try expectDir(io, tmp.dir, "first" ++ Dir.path.sep_str ++ "C");
1316 try expectDir(tmp.dir, "second");1327 try expectDir(io, tmp.dir, "second");
1317 try expectDir(tmp.dir, "third");1328 try expectDir(io, tmp.dir, "third");
1318 },1329 },
1319 }1330 }
1320}1331}
13211332
1322test "makepath ignores '.'" {1333test "makepath ignores '.'" {
1334 const io = testing.io;
1335
1323 var tmp = tmpDir(.{});1336 var tmp = tmpDir(.{});
1324 defer tmp.cleanup();1337 defer tmp.cleanup();
13251338
1326 // Path to create, with "." elements:1339 // Path to create, with "." elements:
1327 const dotPath = try fs.path.join(testing.allocator, &.{1340 const dotPath = try Dir.path.join(testing.allocator, &.{
1328 "first", ".", "second", ".", "third",1341 "first", ".", "second", ".", "third",
1329 });1342 });
1330 defer testing.allocator.free(dotPath);1343 defer testing.allocator.free(dotPath);
13311344
1332 // Path to expect to find:1345 // Path to expect to find:
1333 const expectedPath = try fs.path.join(testing.allocator, &.{1346 const expectedPath = try Dir.path.join(testing.allocator, &.{
1334 "first", "second", "third",1347 "first", "second", "third",
1335 });1348 });
1336 defer testing.allocator.free(expectedPath);1349 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);
1341}1354}
13421355
1343fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void {1356fn testFilenameLimits(io: Io, iterable_dir: Dir, maxed_filename: []const u8, maxed_dirname: []const u8) !void {
1344 // setup, create a dir and a nested file both with maxed filenames, and walk the dir1357 // create a file, a dir, and a nested file all with maxed filenames
1345 {1358 {
1346 var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{});1359 try iterable_dir.writeFile(io, .{ .sub_path = maxed_filename, .data = "" });
1347 defer maxed_dir.close();
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 {
1351 var walker = try iterable_dir.walk(testing.allocator);1391 var walker = try iterable_dir.walk(testing.allocator);
1352 defer walker.deinit();1392 defer walker.deinit();
13531393
1354 var count: usize = 0;1394 var file_count: usize = 0;
1355 while (try walker.next()) |entry| {1395 var dir_count: usize = 0;
1356 try testing.expectEqualStrings(maxed_filename, entry.basename);1396 while (try walker.next(io)) |entry| {
1357 count += 1;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 }
1358 }1408 }
1359 try testing.expectEqual(@as(usize, 2), count);1409 try expectEqual(@as(usize, 2), file_count);
1410 try expectEqual(@as(usize, 1), dir_count);
1360 }1411 }
13611412
1362 // ensure that we can delete the tree1413 // ensure that we can delete the tree
1363 try iterable_dir.deleteTree(maxed_filename);1414 try iterable_dir.deleteTree(io, maxed_filename);
1364}1415}
13651416
1366test "max file name component lengths" {1417test "max file name component lengths" {
1418 const io = testing.io;
1419
1367 var tmp = tmpDir(.{ .iterate = true });1420 var tmp = tmpDir(.{ .iterate = true });
1368 defer tmp.cleanup();1421 defer tmp.cleanup();
13691422
1370 if (native_os == .windows) {1423 if (native_os == .windows) {
1371 // U+FFFF is the character with the largest code point that is encoded as a single1424 // 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.1425 // WTF-16 code unit, so Windows allows for NAME_MAX of them.
1373 const maxed_windows_filename = ("\u{FFFF}".*) ** windows.NAME_MAX;1426 const maxed_windows_filename1 = ("\u{FFFF}".*) ** windows.NAME_MAX;
1374 try testFilenameLimits(tmp.dir, &maxed_windows_filename);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);
1375 } else if (native_os == .wasi) {1431 } else if (native_os == .wasi) {
1376 // On WASI, the maxed filename depends on the host OS, so in order for this test to1432 // On WASI, the maxed filename depends on the host OS, so in order for this test to
1377 // work on any host, we need to use a length that will work for all platforms1433 // work on any host, we need to use a length that will work for all platforms
1378 // (i.e. the minimum max_name_bytes of all supported platforms).1434 // (i.e. the minimum max_name_bytes of all supported platforms).
1379 const maxed_wasi_filename = [_]u8{'1'} ** 255;1435 const maxed_wasi_filename1: [255]u8 = @splat('1');
1380 try testFilenameLimits(tmp.dir, &maxed_wasi_filename);1436 const maxed_wasi_filename2: [255]u8 = @splat('2');
1437 try testFilenameLimits(io, tmp.dir, &maxed_wasi_filename1, &maxed_wasi_filename2);
1381 } else {1438 } else {
1382 const maxed_ascii_filename = [_]u8{'1'} ** std.fs.max_name_bytes;1439 const maxed_ascii_filename1: [Dir.max_name_bytes]u8 = @splat('1');
1383 try testFilenameLimits(tmp.dir, &maxed_ascii_filename);1440 const maxed_ascii_filename2: [Dir.max_name_bytes]u8 = @splat('2');
1441 try testFilenameLimits(io, tmp.dir, &maxed_ascii_filename1, &maxed_ascii_filename2);
1384 }1442 }
1385}1443}
13861444
...@@ -1398,21 +1456,21 @@ test "writev, readv" {...@@ -1398,21 +1456,21 @@ test "writev, readv" {
1398 var write_vecs: [2][]const u8 = .{ line1, line2 };1456 var write_vecs: [2][]const u8 = .{ line1, line2 };
1399 var read_vecs: [2][]u8 = .{ &buf2, &buf1 };1457 var read_vecs: [2][]u8 = .{ &buf2, &buf1 };
14001458
1401 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });1459 var src_file = try tmp.dir.createFile(io, "test.txt", .{ .read = true });
1402 defer src_file.close();1460 defer src_file.close(io);
14031461
1404 var writer = src_file.writerStreaming(&.{});1462 var writer = src_file.writerStreaming(io, &.{});
14051463
1406 try writer.interface.writeVecAll(&write_vecs);1464 try writer.interface.writeVecAll(&write_vecs);
1407 try writer.interface.flush();1465 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();
1411 try reader.seekTo(0);1469 try reader.seekTo(0);
1412 try reader.interface.readVecAll(&read_vecs);1470 try reader.interface.readVecAll(&read_vecs);
1413 try testing.expectEqualStrings(&buf1, "line2\n");1471 try expectEqualStrings(&buf1, "line2\n");
1414 try testing.expectEqualStrings(&buf2, "line1\n");1472 try expectEqualStrings(&buf2, "line1\n");
1415 try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));1473 try expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
1416}1474}
14171475
1418test "pwritev, preadv" {1476test "pwritev, preadv" {
...@@ -1428,87 +1486,37 @@ test "pwritev, preadv" {...@@ -1428,87 +1486,37 @@ test "pwritev, preadv" {
1428 var buf2: [line2.len]u8 = undefined;1486 var buf2: [line2.len]u8 = undefined;
1429 var read_vecs: [2][]u8 = .{ &buf2, &buf1 };1487 var read_vecs: [2][]u8 = .{ &buf2, &buf1 };
14301488
1431 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });1489 var src_file = try tmp.dir.createFile(io, "test.txt", .{ .read = true });
1432 defer src_file.close();1490 defer src_file.close(io);
14331491
1434 var writer = src_file.writer(&.{});1492 var writer = src_file.writer(io, &.{});
14351493
1436 try writer.seekTo(16);1494 try writer.seekTo(16);
1437 try writer.interface.writeVecAll(&lines);1495 try writer.interface.writeVecAll(&lines);
1438 try writer.interface.flush();1496 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();
1442 try reader.seekTo(16);1500 try reader.seekTo(16);
1443 try reader.interface.readVecAll(&read_vecs);1501 try reader.interface.readVecAll(&read_vecs);
1444 try testing.expectEqualStrings(&buf1, "line2\n");1502 try expectEqualStrings(&buf1, "line2\n");
1445 try testing.expectEqualStrings(&buf2, "line1\n");1503 try expectEqualStrings(&buf2, "line1\n");
1446 try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));1504 try 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));
1498}1505}
14991506
1500test "access file" {1507test "access file" {
1501 try testWithAllSupportedPathTypes(struct {1508 try testWithAllSupportedPathTypes(struct {
1502 fn impl(ctx: *TestContext) !void {1509 fn impl(ctx: *TestContext) !void {
1510 const io = ctx.io;
1503 const dir_path = try ctx.transformPath("os_test_tmp");1511 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);1514 try ctx.dir.createDirPath(io, dir_path);
1507 try testing.expectError(error.FileNotFound, ctx.dir.access(file_path, .{}));1515 try expectError(error.FileNotFound, ctx.dir.access(io, file_path, .{}));
15081516
1509 try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "" });1517 try ctx.dir.writeFile(io, .{ .sub_path = file_path, .data = "" });
1510 try ctx.dir.access(file_path, .{});1518 try ctx.dir.access(io, file_path, .{});
1511 try ctx.dir.deleteTree(dir_path);1519 try ctx.dir.deleteTree(io, dir_path);
1512 }1520 }
1513 }.impl);1521 }.impl);
1514}1522}
...@@ -1519,24 +1527,24 @@ test "sendfile" {...@@ -1519,24 +1527,24 @@ test "sendfile" {
1519 var tmp = tmpDir(.{});1527 var tmp = tmpDir(.{});
1520 defer tmp.cleanup();1528 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", .{});1532 var dir = try tmp.dir.openDir(io, "os_test_tmp", .{});
1525 defer dir.close();1533 defer dir.close(io);
15261534
1527 const line1 = "line1\n";1535 const line1 = "line1\n";
1528 const line2 = "second line\n";1536 const line2 = "second line\n";
1529 var vecs = [_][]const u8{ line1, line2 };1537 var vecs = [_][]const u8{ line1, line2 };
15301538
1531 var src_file = try dir.createFile("sendfile1.txt", .{ .read = true });1539 var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true });
1532 defer src_file.close();1540 defer src_file.close(io);
1533 {1541 {
1534 var fw = src_file.writer(&.{});1542 var fw = src_file.writer(io, &.{});
1535 try fw.interface.writeVecAll(&vecs);1543 try fw.interface.writeVecAll(&vecs);
1536 }1544 }
15371545
1538 var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true });1546 var dest_file = try dir.createFile(io, "sendfile2.txt", .{ .read = true });
1539 defer dest_file.close();1547 defer dest_file.close(io);
15401548
1541 const header1 = "header1\n";1549 const header1 = "header1\n";
1542 const header2 = "second header\n";1550 const header2 = "second header\n";
...@@ -1548,16 +1556,16 @@ test "sendfile" {...@@ -1548,16 +1556,16 @@ test "sendfile" {
1548 var written_buf: [100]u8 = undefined;1556 var written_buf: [100]u8 = undefined;
1549 var file_reader = src_file.reader(io, &.{});1557 var file_reader = src_file.reader(io, &.{});
1550 var fallback_buffer: [50]u8 = undefined;1558 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);
1552 try file_writer.interface.writeVecAll(&headers);1560 try file_writer.interface.writeVecAll(&headers);
1553 try file_reader.seekTo(1);1561 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)));
1555 try file_writer.interface.writeVecAll(&trailers);1563 try file_writer.interface.writeVecAll(&trailers);
1556 try file_writer.interface.flush();1564 try file_writer.interface.flush();
1557 var fr = file_writer.moveToReader(io);1565 var fr = file_writer.moveToReader();
1558 try fr.seekTo(0);1566 try fr.seekTo(0);
1559 const amt = try fr.interface.readSliceShort(&written_buf);1567 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]);
1561}1569}
15621570
1563test "sendfile with buffered data" {1571test "sendfile with buffered data" {
...@@ -1566,18 +1574,18 @@ test "sendfile with buffered data" {...@@ -1566,18 +1574,18 @@ test "sendfile with buffered data" {
1566 var tmp = tmpDir(.{});1574 var tmp = tmpDir(.{});
1567 defer tmp.cleanup();1575 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", .{});1579 var dir = try tmp.dir.openDir(io, "os_test_tmp", .{});
1572 defer dir.close();1580 defer dir.close(io);
15731581
1574 var src_file = try dir.createFile("sendfile1.txt", .{ .read = true });1582 var src_file = try dir.createFile(io, "sendfile1.txt", .{ .read = true });
1575 defer src_file.close();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 });1587 var dest_file = try dir.createFile(io, "sendfile2.txt", .{ .read = true });
1580 defer dest_file.close();1588 defer dest_file.close(io);
15811589
1582 var src_buffer: [32]u8 = undefined;1590 var src_buffer: [32]u8 = undefined;
1583 var file_reader = src_file.reader(io, &src_buffer);1591 var file_reader = src_file.reader(io, &src_buffer);
...@@ -1586,52 +1594,54 @@ test "sendfile with buffered data" {...@@ -1586,52 +1594,54 @@ test "sendfile with buffered data" {
1586 try file_reader.interface.fill(8);1594 try file_reader.interface.fill(8);
15871595
1588 var fallback_buffer: [32]u8 = undefined;1596 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
1593 var written_buf: [8]u8 = undefined;1601 var written_buf: [8]u8 = undefined;
1594 var fr = file_writer.moveToReader(io);1602 var fr = file_writer.moveToReader();
1595 try fr.seekTo(0);1603 try fr.seekTo(0);
1596 const amt = try fr.interface.readSliceShort(&written_buf);1604 const amt = try fr.interface.readSliceShort(&written_buf);
15971605
1598 try std.testing.expectEqual(4, amt);1606 try expectEqual(4, amt);
1599 try std.testing.expectEqualSlices(u8, "AAAA", written_buf[0..amt]);1607 try expectEqualSlices(u8, "AAAA", written_buf[0..amt]);
1600}1608}
16011609
1602test "copyFile" {1610test "copyFile" {
1603 try testWithAllSupportedPathTypes(struct {1611 try testWithAllSupportedPathTypes(struct {
1604 fn impl(ctx: *TestContext) !void {1612 fn impl(ctx: *TestContext) !void {
1613 const io = ctx.io;
1605 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";1614 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
1606 const src_file = try ctx.transformPath("tmp_test_copy_file.txt");1615 const src_file = try ctx.transformPath("tmp_test_copy_file.txt");
1607 const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt");1616 const dest_file = try ctx.transformPath("tmp_test_copy_file2.txt");
1608 const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt");1617 const dest_file2 = try ctx.transformPath("tmp_test_copy_file3.txt");
16091618
1610 try ctx.dir.writeFile(.{ .sub_path = src_file, .data = data });1619 try ctx.dir.writeFile(io, .{ .sub_path = src_file, .data = data });
1611 defer ctx.dir.deleteFile(src_file) catch {};1620 defer ctx.dir.deleteFile(io, src_file) catch {};
16121621
1613 try ctx.dir.copyFile(src_file, ctx.dir, dest_file, .{});1622 try ctx.dir.copyFile(src_file, ctx.dir, dest_file, io, .{});
1614 defer ctx.dir.deleteFile(dest_file) catch {};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 });1625 try ctx.dir.copyFile(src_file, ctx.dir, dest_file2, io, .{});
1617 defer ctx.dir.deleteFile(dest_file2) catch {};1626 defer ctx.dir.deleteFile(io, dest_file2) catch {};
16181627
1619 try expectFileContents(ctx.dir, dest_file, data);1628 try expectFileContents(io, ctx.dir, dest_file, data);
1620 try expectFileContents(ctx.dir, dest_file2, data);1629 try expectFileContents(io, ctx.dir, dest_file2, data);
1621 }1630 }
1622 }.impl);1631 }.impl);
1623}1632}
16241633
1625fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {1634fn expectFileContents(io: Io, dir: Dir, file_path: []const u8, data: []const u8) !void {
1626 const contents = try dir.readFileAlloc(file_path, testing.allocator, .limited(1000));1635 const contents = try dir.readFileAlloc(io, file_path, testing.allocator, .limited(1000));
1627 defer testing.allocator.free(contents);1636 defer testing.allocator.free(contents);
16281637
1629 try testing.expectEqualSlices(u8, data, contents);1638 try expectEqualSlices(u8, data, contents);
1630}1639}
16311640
1632test "AtomicFile" {1641test "AtomicFile" {
1633 try testWithAllSupportedPathTypes(struct {1642 try testWithAllSupportedPathTypes(struct {
1634 fn impl(ctx: *TestContext) !void {1643 fn impl(ctx: *TestContext) !void {
1644 const io = ctx.io;
1635 const allocator = ctx.arena.allocator();1645 const allocator = ctx.arena.allocator();
1636 const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt");1646 const test_out_file = try ctx.transformPath("tmp_atomic_file_test_dest.txt");
1637 const test_content =1647 const test_content =
...@@ -1641,15 +1651,15 @@ test "AtomicFile" {...@@ -1641,15 +1651,15 @@ test "AtomicFile" {
16411651
1642 {1652 {
1643 var buffer: [100]u8 = undefined;1653 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 });
1645 defer af.deinit();1655 defer af.deinit();
1646 try af.file_writer.interface.writeAll(test_content);1656 try af.file_writer.interface.writeAll(test_content);
1647 try af.finish();1657 try af.finish();
1648 }1658 }
1649 const content = try ctx.dir.readFileAlloc(test_out_file, allocator, .limited(9999));1659 const content = try ctx.dir.readFileAlloc(io, test_out_file, allocator, .limited(9999));
1650 try testing.expectEqualStrings(test_content, content);1660 try expectEqualStrings(test_content, content);
16511661
1652 try ctx.dir.deleteFile(test_out_file);1662 try ctx.dir.deleteFile(io, test_out_file);
1653 }1663 }
1654 }.impl);1664 }.impl);
1655}1665}
...@@ -1659,13 +1669,14 @@ test "open file with exclusive nonblocking lock twice" {...@@ -1659,13 +1669,14 @@ test "open file with exclusive nonblocking lock twice" {
16591669
1660 try testWithAllSupportedPathTypes(struct {1670 try testWithAllSupportedPathTypes(struct {
1661 fn impl(ctx: *TestContext) !void {1671 fn impl(ctx: *TestContext) !void {
1672 const io = ctx.io;
1662 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");1673 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
16631674
1664 const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });1675 const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1665 defer file1.close();1676 defer file1.close(io);
16661677
1667 const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });1678 const file2 = ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1668 try testing.expectError(error.WouldBlock, file2);1679 try expectError(error.WouldBlock, file2);
1669 }1680 }
1670 }.impl);1681 }.impl);
1671}1682}
...@@ -1675,13 +1686,14 @@ test "open file with shared and exclusive nonblocking lock" {...@@ -1675,13 +1686,14 @@ test "open file with shared and exclusive nonblocking lock" {
16751686
1676 try testWithAllSupportedPathTypes(struct {1687 try testWithAllSupportedPathTypes(struct {
1677 fn impl(ctx: *TestContext) !void {1688 fn impl(ctx: *TestContext) !void {
1689 const io = ctx.io;
1678 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");1690 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
16791691
1680 const file1 = try ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });1692 const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .shared, .lock_nonblocking = true });
1681 defer file1.close();1693 defer file1.close(io);
16821694
1683 const file2 = ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });1695 const file2 = ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1684 try testing.expectError(error.WouldBlock, file2);1696 try expectError(error.WouldBlock, file2);
1685 }1697 }
1686 }.impl);1698 }.impl);
1687}1699}
...@@ -1691,13 +1703,14 @@ test "open file with exclusive and shared nonblocking lock" {...@@ -1691,13 +1703,14 @@ test "open file with exclusive and shared nonblocking lock" {
16911703
1692 try testWithAllSupportedPathTypes(struct {1704 try testWithAllSupportedPathTypes(struct {
1693 fn impl(ctx: *TestContext) !void {1705 fn impl(ctx: *TestContext) !void {
1706 const io = ctx.io;
1694 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");1707 const filename = try ctx.transformPath("file_nonblocking_lock_test.txt");
16951708
1696 const file1 = try ctx.dir.createFile(filename, .{ .lock = .exclusive, .lock_nonblocking = true });1709 const file1 = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive, .lock_nonblocking = true });
1697 defer file1.close();1710 defer file1.close(io);
16981711
1699 const file2 = ctx.dir.createFile(filename, .{ .lock = .shared, .lock_nonblocking = true });1712 const file2 = ctx.dir.createFile(io, filename, .{ .lock = .shared, .lock_nonblocking = true });
1700 try testing.expectError(error.WouldBlock, file2);1713 try expectError(error.WouldBlock, file2);
1701 }1714 }
1702 }.impl);1715 }.impl);
1703}1716}
...@@ -1707,39 +1720,35 @@ test "open file with exclusive lock twice, make sure second lock waits" {...@@ -1707,39 +1720,35 @@ test "open file with exclusive lock twice, make sure second lock waits" {
17071720
1708 try testWithAllSupportedPathTypes(struct {1721 try testWithAllSupportedPathTypes(struct {
1709 fn impl(ctx: *TestContext) !void {1722 fn impl(ctx: *TestContext) !void {
1723 const io = ctx.io;
1710 const filename = try ctx.transformPath("file_lock_test.txt");1724 const filename = try ctx.transformPath("file_lock_test.txt");
17111725
1712 const file = try ctx.dir.createFile(filename, .{ .lock = .exclusive });1726 const file = try ctx.dir.createFile(io, filename, .{ .lock = .exclusive });
1713 errdefer file.close();1727 errdefer file.close(io);
17141728
1715 const S = struct {1729 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 {
1717 started.set();1731 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
1720 locked.set();1734 locked.set();
1721 file1.close();1735 file1.close(inner_ctx.io);
1722 }1736 }
1723 };1737 };
17241738
1725 var started: std.Thread.ResetEvent = .unset;1739 var started: std.Thread.ResetEvent = .unset;
1726 var locked: std.Thread.ResetEvent = .unset;1740 var locked: std.Thread.ResetEvent = .unset;
17271741
1728 const t = try std.Thread.spawn(.{}, S.checkFn, .{1742 const t = try std.Thread.spawn(.{}, S.checkFn, .{ ctx, filename, &started, &locked });
1729 &ctx.dir,
1730 filename,
1731 &started,
1732 &locked,
1733 });
1734 defer t.join();1743 defer t.join();
17351744
1736 // Wait for the spawned thread to start trying to acquire the exclusive file lock.1745 // Wait for the spawned thread to start trying to acquire the exclusive file lock.
1737 // Then wait a bit to make sure that can't acquire it since we currently hold the file lock.1746 // Then wait a bit to make sure that can't acquire it since we currently hold the file lock.
1738 started.wait();1747 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
1741 // Release the file lock which should unlock the thread to lock it and set the locked event.1750 // Release the file lock which should unlock the thread to lock it and set the locked event.
1742 file.close();1751 file.close(io);
1743 locked.wait();1752 locked.wait();
1744 }1753 }
1745 }.impl);1754 }.impl);
...@@ -1748,11 +1757,13 @@ test "open file with exclusive lock twice, make sure second lock waits" {...@@ -1748,11 +1757,13 @@ test "open file with exclusive lock twice, make sure second lock waits" {
1748test "open file with exclusive nonblocking lock twice (absolute paths)" {1757test "open file with exclusive nonblocking lock twice (absolute paths)" {
1749 if (native_os == .wasi) return error.SkipZigTest;1758 if (native_os == .wasi) return error.SkipZigTest;
17501759
1760 const io = testing.io;
1761
1751 var random_bytes: [12]u8 = undefined;1762 var random_bytes: [12]u8 = undefined;
1752 std.crypto.random.bytes(&random_bytes);1763 std.crypto.random.bytes(&random_bytes);
17531764
1754 var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;1765 var random_b64: [std.fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
1755 _ = fs.base64_encoder.encode(&random_b64, &random_bytes);1766 _ = std.fs.base64_encoder.encode(&random_b64, &random_bytes);
17561767
1757 const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt";1768 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)" {...@@ -1761,47 +1772,50 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
1761 const cwd = try std.process.getCwdAlloc(gpa);1772 const cwd = try std.process.getCwdAlloc(gpa);
1762 defer gpa.free(cwd);1773 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 });
1765 defer gpa.free(filename);1776 defer gpa.free(filename);
17661777
1767 defer fs.deleteFileAbsolute(filename) catch {}; // createFileAbsolute can leave files on failures1778 defer Dir.deleteFileAbsolute(io, filename) catch {}; // createFileAbsolute can leave files on failures
1768 const file1 = try fs.createFileAbsolute(filename, .{1779 const file1 = try Dir.createFileAbsolute(io, filename, .{
1769 .lock = .exclusive,1780 .lock = .exclusive,
1770 .lock_nonblocking = true,1781 .lock_nonblocking = true,
1771 });1782 });
17721783
1773 const file2 = fs.createFileAbsolute(filename, .{1784 const file2 = Dir.createFileAbsolute(io, filename, .{
1774 .lock = .exclusive,1785 .lock = .exclusive,
1775 .lock_nonblocking = true,1786 .lock_nonblocking = true,
1776 });1787 });
1777 file1.close();1788 file1.close(io);
1778 try testing.expectError(error.WouldBlock, file2);1789 try expectError(error.WouldBlock, file2);
1779}1790}
17801791
1781test "read from locked file" {1792test "read from locked file" {
1782 try testWithAllSupportedPathTypes(struct {1793 try testWithAllSupportedPathTypes(struct {
1783 fn impl(ctx: *TestContext) !void {1794 fn impl(ctx: *TestContext) !void {
1795 const io = ctx.io;
1784 const filename = try ctx.transformPath("read_lock_file_test.txt");1796 const filename = try ctx.transformPath("read_lock_file_test.txt");
17851797
1786 {1798 {
1787 const f = try ctx.dir.createFile(filename, .{ .read = true });1799 const f = try ctx.dir.createFile(io, filename, .{ .read = true });
1788 defer f.close();1800 defer f.close(io);
1789 var buffer: [1]u8 = undefined;1801 var buffer: [1]u8 = undefined;
1790 _ = try f.read(&buffer);1802 _ = try f.readPositional(io, &.{&buffer}, 0);
1791 }1803 }
1792 {1804 {
1793 const f = try ctx.dir.createFile(filename, .{1805 const f = try ctx.dir.createFile(io, filename, .{
1794 .read = true,1806 .read = true,
1795 .lock = .exclusive,1807 .lock = .exclusive,
1796 });1808 });
1797 defer f.close();1809 defer f.close(io);
1798 const f2 = try ctx.dir.openFile(filename, .{});1810 const f2 = try ctx.dir.openFile(io, filename, .{});
1799 defer f2.close();1811 defer f2.close(io);
1812 // On POSIX locks may be ignored, however on Windows they cause
1813 // LockViolation.
1800 var buffer: [1]u8 = undefined;1814 var buffer: [1]u8 = undefined;
1801 if (builtin.os.tag == .windows) {1815 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));
1803 } else {1817 } else {
1804 try std.testing.expectEqual(0, f2.read(&buffer));1818 try expectEqual(0, f2.readPositional(io, &.{&buffer}, 0));
1805 }1819 }
1806 }1820 }
1807 }1821 }
...@@ -1809,6 +1823,8 @@ test "read from locked file" {...@@ -1809,6 +1823,8 @@ test "read from locked file" {
1809}1823}
18101824
1811test "walker" {1825test "walker" {
1826 const io = testing.io;
1827
1812 var tmp = tmpDir(.{ .iterate = true });1828 var tmp = tmpDir(.{ .iterate = true });
1813 defer tmp.cleanup();1829 defer tmp.cleanup();
18141830
...@@ -1819,9 +1835,9 @@ test "walker" {...@@ -1819,9 +1835,9 @@ test "walker" {
1819 .{ "dir2", 1 },1835 .{ "dir2", 1 },
1820 .{ "dir3", 1 },1836 .{ "dir3", 1 },
1821 .{ "dir4", 1 },1837 .{ "dir4", 1 },
1822 .{ "dir3" ++ fs.path.sep_str ++ "sub1", 2 },1838 .{ "dir3" ++ Dir.path.sep_str ++ "sub1", 2 },
1823 .{ "dir3" ++ fs.path.sep_str ++ "sub2", 2 },1839 .{ "dir3" ++ Dir.path.sep_str ++ "sub2", 2 },
1824 .{ "dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1", 3 },1840 .{ "dir3" ++ Dir.path.sep_str ++ "sub2" ++ Dir.path.sep_str ++ "subsub1", 3 },
1825 });1841 });
18261842
1827 const expected_basenames = std.StaticStringMap(void).initComptime(.{1843 const expected_basenames = std.StaticStringMap(void).initComptime(.{
...@@ -1835,35 +1851,37 @@ test "walker" {...@@ -1835,35 +1851,37 @@ test "walker" {
1835 });1851 });
18361852
1837 for (expected_paths.keys()) |key| {1853 for (expected_paths.keys()) |key| {
1838 try tmp.dir.makePath(key);1854 try tmp.dir.createDirPath(io, key);
1839 }1855 }
18401856
1841 var walker = try tmp.dir.walk(testing.allocator);1857 var walker = try tmp.dir.walk(testing.allocator);
1842 defer walker.deinit();1858 defer walker.deinit();
18431859
1844 var num_walked: usize = 0;1860 var num_walked: usize = 0;
1845 while (try walker.next()) |entry| {1861 while (try walker.next(io)) |entry| {
1846 testing.expect(expected_basenames.has(entry.basename)) catch |err| {1862 expect(expected_basenames.has(entry.basename)) catch |err| {
1847 std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)});1863 std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)});
1848 return err;1864 return err;
1849 };1865 };
1850 testing.expect(expected_paths.has(entry.path)) catch |err| {1866 expect(expected_paths.has(entry.path)) catch |err| {
1851 std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});1867 std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
1852 return err;1868 return err;
1853 };1869 };
1854 testing.expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| {1870 expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| {
1855 std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});1871 std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
1856 return err;1872 return err;
1857 };1873 };
1858 // make sure that the entry.dir is the containing dir1874 // make sure that the entry.dir is the containing dir
1859 var entry_dir = try entry.dir.openDir(entry.basename, .{});1875 var entry_dir = try entry.dir.openDir(io, entry.basename, .{});
1860 defer entry_dir.close();1876 defer entry_dir.close(io);
1861 num_walked += 1;1877 num_walked += 1;
1862 }1878 }
1863 try testing.expectEqual(expected_paths.kvs.len, num_walked);1879 try expectEqual(expected_paths.kvs.len, num_walked);
1864}1880}
18651881
1866test "selective walker, skip entries that start with ." {1882test "selective walker, skip entries that start with ." {
1883 const io = testing.io;
1884
1867 var tmp = tmpDir(.{ .iterate = true });1885 var tmp = tmpDir(.{ .iterate = true });
1868 defer tmp.cleanup();1886 defer tmp.cleanup();
18691887
...@@ -1878,11 +1896,11 @@ test "selective walker, skip entries that start with ." {...@@ -1878,11 +1896,11 @@ test "selective walker, skip entries that start with ." {
18781896
1879 const expected_paths = std.StaticStringMap(usize).initComptime(.{1897 const expected_paths = std.StaticStringMap(usize).initComptime(.{
1880 .{ "dir1", 1 },1898 .{ "dir1", 1 },
1881 .{ "dir1" ++ fs.path.sep_str ++ "foo", 2 },1899 .{ "dir1" ++ Dir.path.sep_str ++ "foo", 2 },
1882 .{ "a", 1 },1900 .{ "a", 1 },
1883 .{ "a" ++ fs.path.sep_str ++ "b", 2 },1901 .{ "a" ++ Dir.path.sep_str ++ "b", 2 },
1884 .{ "a" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c", 3 },1902 .{ "a" ++ Dir.path.sep_str ++ "b" ++ Dir.path.sep_str ++ "c", 3 },
1885 .{ "a" ++ fs.path.sep_str ++ "baz", 2 },1903 .{ "a" ++ Dir.path.sep_str ++ "baz", 2 },
1886 });1904 });
18871905
1888 const expected_basenames = std.StaticStringMap(void).initComptime(.{1906 const expected_basenames = std.StaticStringMap(void).initComptime(.{
...@@ -1895,41 +1913,43 @@ test "selective walker, skip entries that start with ." {...@@ -1895,41 +1913,43 @@ test "selective walker, skip entries that start with ." {
1895 });1913 });
18961914
1897 for (paths_to_create) |path| {1915 for (paths_to_create) |path| {
1898 try tmp.dir.makePath(path);1916 try tmp.dir.createDirPath(io, path);
1899 }1917 }
19001918
1901 var walker = try tmp.dir.walkSelectively(testing.allocator);1919 var walker = try tmp.dir.walkSelectively(testing.allocator);
1902 defer walker.deinit();1920 defer walker.deinit();
19031921
1904 var num_walked: usize = 0;1922 var num_walked: usize = 0;
1905 while (try walker.next()) |entry| {1923 while (try walker.next(io)) |entry| {
1906 if (entry.basename[0] == '.') continue;1924 if (entry.basename[0] == '.') continue;
1907 if (entry.kind == .directory) {1925 if (entry.kind == .directory) {
1908 try walker.enter(entry);1926 try walker.enter(io, entry);
1909 }1927 }
19101928
1911 testing.expect(expected_basenames.has(entry.basename)) catch |err| {1929 expect(expected_basenames.has(entry.basename)) catch |err| {
1912 std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)});1930 std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)});
1913 return err;1931 return err;
1914 };1932 };
1915 testing.expect(expected_paths.has(entry.path)) catch |err| {1933 expect(expected_paths.has(entry.path)) catch |err| {
1916 std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});1934 std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
1917 return err;1935 return err;
1918 };1936 };
1919 testing.expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| {1937 expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| {
1920 std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});1938 std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)});
1921 return err;1939 return err;
1922 };1940 };
19231941
1924 // make sure that the entry.dir is the containing dir1942 // make sure that the entry.dir is the containing dir
1925 var entry_dir = try entry.dir.openDir(entry.basename, .{});1943 var entry_dir = try entry.dir.openDir(io, entry.basename, .{});
1926 defer entry_dir.close();1944 defer entry_dir.close(io);
1927 num_walked += 1;1945 num_walked += 1;
1928 }1946 }
1929 try testing.expectEqual(expected_paths.kvs.len, num_walked);1947 try expectEqual(expected_paths.kvs.len, num_walked);
1930}1948}
19311949
1932test "walker without fully iterating" {1950test "walker without fully iterating" {
1951 const io = testing.io;
1952
1933 var tmp = tmpDir(.{ .iterate = true });1953 var tmp = tmpDir(.{ .iterate = true });
1934 defer tmp.cleanup();1954 defer tmp.cleanup();
19351955
...@@ -1939,18 +1959,18 @@ test "walker without fully iterating" {...@@ -1939,18 +1959,18 @@ test "walker without fully iterating" {
1939 // Create 2 directories inside the tmp directory, but then only iterate once before breaking.1959 // Create 2 directories inside the tmp directory, but then only iterate once before breaking.
1940 // This ensures that walker doesn't try to close the initial directory when not fully iterating.1960 // This ensures that walker doesn't try to close the initial directory when not fully iterating.
19411961
1942 try tmp.dir.makePath("a");1962 try tmp.dir.createDirPath(io, "a");
1943 try tmp.dir.makePath("b");1963 try tmp.dir.createDirPath(io, "b");
19441964
1945 var num_walked: usize = 0;1965 var num_walked: usize = 0;
1946 while (try walker.next()) |_| {1966 while (try walker.next(io)) |_| {
1947 num_walked += 1;1967 num_walked += 1;
1948 break;1968 break;
1949 }1969 }
1950 try testing.expectEqual(@as(usize, 1), num_walked);1970 try expectEqual(@as(usize, 1), num_walked);
1951}1971}
19521972
1953test "'.' and '..' in fs.Dir functions" {1973test "'.' and '..' in Dir functions" {
1954 if (native_os == .windows and builtin.cpu.arch == .aarch64) {1974 if (native_os == .windows and builtin.cpu.arch == .aarch64) {
1955 // https://github.com/ziglang/zig/issues/171341975 // https://github.com/ziglang/zig/issues/17134
1956 return error.SkipZigTest;1976 return error.SkipZigTest;
...@@ -1965,27 +1985,27 @@ test "'.' and '..' in fs.Dir functions" {...@@ -1965,27 +1985,27 @@ test "'.' and '..' in fs.Dir functions" {
1965 const rename_path = try ctx.transformPath("./subdir/../rename");1985 const rename_path = try ctx.transformPath("./subdir/../rename");
1966 const update_path = try ctx.transformPath("./subdir/../update");1986 const update_path = try ctx.transformPath("./subdir/../update");
19671987
1968 try ctx.dir.makeDir(subdir_path);1988 try ctx.dir.createDir(io, subdir_path, .default_dir);
1969 try ctx.dir.access(subdir_path, .{});1989 try ctx.dir.access(io, subdir_path, .{});
1970 var created_subdir = try ctx.dir.openDir(subdir_path, .{});1990 var created_subdir = try ctx.dir.openDir(io, subdir_path, .{});
1971 created_subdir.close();1991 created_subdir.close(io);
19721992
1973 const created_file = try ctx.dir.createFile(file_path, .{});1993 const created_file = try ctx.dir.createFile(io, file_path, .{});
1974 created_file.close();1994 created_file.close(io);
1975 try ctx.dir.access(file_path, .{});1995 try ctx.dir.access(io, file_path, .{});
19761996
1977 try ctx.dir.copyFile(file_path, ctx.dir, copy_path, .{});1997 try ctx.dir.copyFile(file_path, ctx.dir, copy_path, io, .{});
1978 try ctx.dir.rename(copy_path, rename_path);1998 try ctx.dir.rename(copy_path, ctx.dir, rename_path, io);
1979 const renamed_file = try ctx.dir.openFile(rename_path, .{});1999 const renamed_file = try ctx.dir.openFile(io, rename_path, .{});
1980 renamed_file.close();2000 renamed_file.close(io);
1981 try ctx.dir.deleteFile(rename_path);2001 try ctx.dir.deleteFile(io, rename_path);
19822002
1983 try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" });2003 try ctx.dir.writeFile(io, .{ .sub_path = update_path, .data = "something" });
1984 var dir = ctx.dir.adaptToNewApi();2004 var dir = ctx.dir;
1985 const prev_status = try dir.updateFile(io, file_path, dir, update_path, .{});2005 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);
1989 }2009 }
1990 }.impl);2010 }.impl);
1991}2011}
...@@ -1994,6 +2014,8 @@ test "'.' and '..' in absolute functions" {...@@ -1994,6 +2014,8 @@ test "'.' and '..' in absolute functions" {
1994 if (native_os == .wasi) return error.SkipZigTest;2014 if (native_os == .wasi) return error.SkipZigTest;
1995 if (native_os == .openbsd) return error.SkipZigTest;2015 if (native_os == .openbsd) return error.SkipZigTest;
19962016
2017 const io = testing.io;
2018
1997 var tmp = tmpDir(.{});2019 var tmp = tmpDir(.{});
1998 defer tmp.cleanup();2020 defer tmp.cleanup();
19992021
...@@ -2001,83 +2023,71 @@ test "'.' and '..' in absolute functions" {...@@ -2001,83 +2023,71 @@ test "'.' and '..' in absolute functions" {
2001 defer arena.deinit();2023 defer arena.deinit();
2002 const allocator = arena.allocator();2024 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" });2028 const subdir_path = try Dir.path.join(allocator, &.{ base_path, "./subdir" });
2007 try fs.makeDirAbsolute(subdir_path);2029 try Dir.createDirAbsolute(io, subdir_path, .default_dir);
2008 try fs.accessAbsolute(subdir_path, .{});2030 try Dir.accessAbsolute(io, subdir_path, .{});
2009 var created_subdir = try fs.openDirAbsolute(subdir_path, .{});2031 var created_subdir = try Dir.openDirAbsolute(io, subdir_path, .{});
2010 created_subdir.close();2032 created_subdir.close(io);
20112033
2012 const created_file_path = try fs.path.join(allocator, &.{ subdir_path, "../file" });2034 const created_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../file" });
2013 const created_file = try fs.createFileAbsolute(created_file_path, .{});2035 const created_file = try Dir.createFileAbsolute(io, created_file_path, .{});
2014 created_file.close();2036 created_file.close(io);
2015 try fs.accessAbsolute(created_file_path, .{});2037 try Dir.accessAbsolute(io, created_file_path, .{});
20162038
2017 const copied_file_path = try fs.path.join(allocator, &.{ subdir_path, "../copy" });2039 const copied_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../copy" });
2018 try fs.copyFileAbsolute(created_file_path, copied_file_path, .{});2040 try Dir.copyFileAbsolute(created_file_path, copied_file_path, io, .{});
2019 const renamed_file_path = try fs.path.join(allocator, &.{ subdir_path, "../rename" });2041 const renamed_file_path = try Dir.path.join(allocator, &.{ subdir_path, "../rename" });
2020 try fs.renameAbsolute(copied_file_path, renamed_file_path);2042 try Dir.renameAbsolute(copied_file_path, renamed_file_path, io);
2021 const renamed_file = try fs.openFileAbsolute(renamed_file_path, .{});2043 const renamed_file = try Dir.openFileAbsolute(io, renamed_file_path, .{});
2022 renamed_file.close();2044 renamed_file.close(io);
2023 try fs.deleteFileAbsolute(renamed_file_path);2045 try Dir.deleteFileAbsolute(io, renamed_file_path);
20242046
2025 try fs.deleteDirAbsolute(subdir_path);2047 try Dir.deleteDirAbsolute(io, subdir_path);
2026}2048}
20272049
2028test "chmod" {2050test "chmod" {
2029 if (native_os == .windows or native_os == .wasi)2051 if (native_os == .windows or native_os == .wasi) return;
2030 return error.SkipZigTest;2052
2053 const io = testing.io;
20312054
2032 var tmp = tmpDir(.{});2055 var tmp = tmpDir(.{});
2033 defer tmp.cleanup();2056 defer tmp.cleanup();
20342057
2035 const file = try tmp.dir.createFile("test_file", .{ .mode = 0o600 });2058 const file = try tmp.dir.createFile(io, "test_file", .{ .permissions = .fromMode(0o600) });
2036 defer file.close();2059 defer file.close(io);
2037 try testing.expectEqual(@as(File.Mode, 0o600), (try file.stat()).mode & 0o7777);2060 try expectEqual(0o600, (try file.stat(io)).permissions.toMode() & 0o7777);
20382061
2039 try file.chmod(0o644);2062 try file.setPermissions(io, .fromMode(0o644));
2040 try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777);2063 try expectEqual(0o644, (try file.stat(io)).permissions.toMode() & 0o7777);
20412064
2042 try tmp.dir.makeDir("test_dir");2065 try tmp.dir.createDir(io, "test_dir", .default_dir);
2043 var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });2066 var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true });
2044 defer dir.close();2067 defer dir.close(io);
20452068
2046 try dir.chmod(0o700);2069 try dir.setPermissions(io, .fromMode(0o700));
2047 try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777);2070 try expectEqual(0o700, (try dir.stat(io)).permissions.toMode() & 0o7777);
2048}2071}
20492072
2050test "chown" {2073test "change ownership" {
2051 if (native_os == .windows or native_os == .wasi)2074 if (native_os == .windows or native_os == .wasi)
2052 return error.SkipZigTest;2075 return error.SkipZigTest;
20532076
2077 const io = testing.io;
2078
2054 var tmp = tmpDir(.{});2079 var tmp = tmpDir(.{});
2055 defer tmp.cleanup();2080 defer tmp.cleanup();
20562081
2057 const file = try tmp.dir.createFile("test_file", .{});2082 const file = try tmp.dir.createFile(io, "test_file", .{});
2058 defer file.close();2083 defer file.close(io);
2059 try file.chown(null, null);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 });2088 var dir = try tmp.dir.openDir(io, "test_dir", .{ .iterate = true });
2064 defer dir.close();2089 defer dir.close(io);
2065 try dir.chown(null, null);2090 try dir.setOwner(io, 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();
2081}2091}
20822092
2083test "invalid UTF-8/WTF-8 paths" {2093test "invalid UTF-8/WTF-8 paths" {
...@@ -2093,71 +2103,65 @@ test "invalid UTF-8/WTF-8 paths" {...@@ -2093,71 +2103,65 @@ test "invalid UTF-8/WTF-8 paths" {
2093 // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte2103 // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
2094 const invalid_path = try ctx.transformPath("\xFF");2104 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));2112 try expectError(expected_err, ctx.dir.createDirPath(io, invalid_path));
2103 try testing.expectError(expected_err, ctx.dir.makeOpenPath(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, .{}));2123 try expectError(expected_err, ctx.dir.symLink(io, invalid_path, invalid_path, .{}));
2114 if (native_os == .wasi) {
2115 try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));
2116 }
21172124
2118 try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));2125 try expectError(expected_err, ctx.dir.readLink(io, invalid_path, &[_]u8{}));
2119 if (native_os == .wasi) {
2120 try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));
2121 }
21222126
2123 try testing.expectError(expected_err, ctx.dir.readFile(invalid_path, &[_]u8{}));2127 try expectError(expected_err, ctx.dir.readFile(io, invalid_path, &[_]u8{}));
2124 try testing.expectError(expected_err, ctx.dir.readFileAlloc(invalid_path, testing.allocator, .limited(0)));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));2130 try expectError(expected_err, ctx.dir.deleteTree(io, invalid_path));
2127 try testing.expectError(expected_err, ctx.dir.deleteTreeMinStackSize(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();2137 var dir = ctx.dir;
2134 try testing.expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{}));2138 try 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, .{}));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
2139 if (native_os != .wasi) {2143 if (native_os != .wasi) {
2140 try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));2144 try expectError(expected_err, ctx.dir.realPathFile(io, invalid_path, &[_]u8{}));
2141 try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));2145 try expectError(expected_err, ctx.dir.realPathFileAlloc(io, invalid_path, testing.allocator));
2142 }2146 }
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
2146 if (native_os != .wasi and ctx.path_type != .relative) {2150 if (native_os != .wasi and ctx.path_type != .relative) {
2147 try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));2151 var buf: [Dir.max_path_bytes]u8 = undefined;
2148 try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));2152 try expectError(expected_err, Dir.copyFileAbsolute(invalid_path, invalid_path, io, .{}));
2149 try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));2153 try expectError(expected_err, Dir.createDirAbsolute(io, invalid_path, .default_dir));
2150 try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));2154 try expectError(expected_err, Dir.deleteDirAbsolute(io, invalid_path));
2151 try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));2155 try expectError(expected_err, Dir.renameAbsolute(invalid_path, invalid_path, io));
2152 try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));2156 try expectError(expected_err, Dir.openDirAbsolute(io, invalid_path, .{}));
2153 try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));2157 try expectError(expected_err, Dir.openFileAbsolute(io, invalid_path, .{}));
2154 try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));2158 try expectError(expected_err, Dir.accessAbsolute(io, invalid_path, .{}));
2155 try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));2159 try expectError(expected_err, Dir.createFileAbsolute(io, invalid_path, .{}));
2156 try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));2160 try expectError(expected_err, Dir.deleteFileAbsolute(io, invalid_path));
2157 var readlink_buf: [fs.max_path_bytes]u8 = undefined;2161 try expectError(expected_err, Dir.readLinkAbsolute(io, invalid_path, &buf));
2158 try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));2162 try expectError(expected_err, Dir.symLinkAbsolute(io, invalid_path, invalid_path, .{}));
2159 try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));2163 try expectError(expected_err, Dir.realPathFileAbsolute(io, invalid_path, &buf));
2160 try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));2164 try expectError(expected_err, Dir.realPathFileAbsoluteAlloc(io, invalid_path, testing.allocator));
2161 }2165 }
2162 }2166 }
2163 }.impl);2167 }.impl);
...@@ -2171,15 +2175,15 @@ test "read file non vectored" {...@@ -2171,15 +2175,15 @@ test "read file non vectored" {
21712175
2172 const contents = "hello, world!\n";2176 const contents = "hello, world!\n";
21732177
2174 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });2178 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2175 defer file.close();2179 defer file.close(io);
2176 {2180 {
2177 var file_writer: std.fs.File.Writer = .init(file, &.{});2181 var file_writer: File.Writer = .init(file, io, &.{});
2178 try file_writer.interface.writeAll(contents);2182 try file_writer.interface.writeAll(contents);
2179 try file_writer.interface.flush();2183 try file_writer.interface.flush();
2180 }2184 }
21812185
2182 var file_reader: std.Io.File.Reader = .initAdapted(file, io, &.{});2186 var file_reader: std.Io.File.Reader = .init(file, io, &.{});
21832187
2184 var write_buffer: [100]u8 = undefined;2188 var write_buffer: [100]u8 = undefined;
2185 var w: std.Io.Writer = .fixed(&write_buffer);2189 var w: std.Io.Writer = .fixed(&write_buffer);
...@@ -2191,8 +2195,8 @@ test "read file non vectored" {...@@ -2191,8 +2195,8 @@ test "read file non vectored" {
2191 else => |e| return e,2195 else => |e| return e,
2192 };2196 };
2193 }2197 }
2194 try testing.expectEqualStrings(contents, w.buffered());2198 try expectEqualStrings(contents, w.buffered());
2195 try testing.expectEqual(contents.len, i);2199 try expectEqual(contents.len, i);
2196}2200}
21972201
2198test "seek keeping partial buffer" {2202test "seek keeping partial buffer" {
...@@ -2203,18 +2207,18 @@ test "seek keeping partial buffer" {...@@ -2203,18 +2207,18 @@ test "seek keeping partial buffer" {
22032207
2204 const contents = "0123456789";2208 const contents = "0123456789";
22052209
2206 const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true });2210 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2207 defer file.close();2211 defer file.close(io);
2208 {2212 {
2209 var file_writer: std.fs.File.Writer = .init(file, &.{});2213 var file_writer: File.Writer = .init(file, io, &.{});
2210 try file_writer.interface.writeAll(contents);2214 try file_writer.interface.writeAll(contents);
2211 try file_writer.interface.flush();2215 try file_writer.interface.flush();
2212 }2216 }
22132217
2214 var read_buffer: [3]u8 = undefined;2218 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
2219 var buf: [4]u8 = undefined;2223 var buf: [4]u8 = undefined;
2220 try file_reader.interface.readSliceAll(&buf);2224 try file_reader.interface.readSliceAll(&buf);
...@@ -2224,18 +2228,18 @@ test "seek keeping partial buffer" {...@@ -2224,18 +2228,18 @@ test "seek keeping partial buffer" {
2224 return;2228 return;
2225 }2229 }
22262230
2227 try testing.expectEqual(4, file_reader.logicalPos());2231 try expectEqual(4, file_reader.logicalPos());
2228 try testing.expectEqual(7, file_reader.pos);2232 try expectEqual(7, file_reader.pos);
2229 try file_reader.seekTo(6);2233 try file_reader.seekTo(6);
2230 try testing.expectEqual(6, file_reader.logicalPos());2234 try expectEqual(6, file_reader.logicalPos());
2231 try testing.expectEqual(7, file_reader.pos);2235 try expectEqual(7, file_reader.pos);
22322236
2233 try testing.expectEqualStrings("0123", &buf);2237 try expectEqualStrings("0123", &buf);
22342238
2235 const n = try file_reader.interface.readSliceShort(&buf);2239 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);
2239}2243}
22402244
2241test "seekBy" {2245test "seekBy" {
...@@ -2244,16 +2248,16 @@ test "seekBy" {...@@ -2244,16 +2248,16 @@ test "seekBy" {
2244 var tmp_dir = testing.tmpDir(.{});2248 var tmp_dir = testing.tmpDir(.{});
2245 defer tmp_dir.cleanup();2249 defer tmp_dir.cleanup();
22462250
2247 try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" });2251 try tmp_dir.dir.writeFile(io, .{ .sub_path = "blah.txt", .data = "let's test seekBy" });
2248 const f = try tmp_dir.dir.openFile("blah.txt", .{ .mode = .read_only });2252 const f = try tmp_dir.dir.openFile(io, "blah.txt", .{ .mode = .read_only });
2249 defer f.close();2253 defer f.close(io);
2250 var reader = f.readerStreaming(io, &.{});2254 var reader = f.readerStreaming(io, &.{});
2251 try reader.seekBy(2);2255 try reader.seekBy(2);
22522256
2253 var buffer: [20]u8 = undefined;2257 var buffer: [20]u8 = undefined;
2254 const n = try reader.interface.readSliceShort(&buffer);2258 const n = try reader.interface.readSliceShort(&buffer);
2255 try testing.expectEqual(15, n);2259 try expectEqual(15, n);
2256 try testing.expectEqualStrings("t's test seekBy", buffer[0..15]);2260 try expectEqualStrings("t's test seekBy", buffer[0..15]);
2257}2261}
22582262
2259test "seekTo flushes buffered data" {2263test "seekTo flushes buffered data" {
...@@ -2264,11 +2268,11 @@ test "seekTo flushes buffered data" {...@@ -2264,11 +2268,11 @@ test "seekTo flushes buffered data" {
22642268
2265 const contents = "data";2269 const contents = "data";
22662270
2267 const file = try tmp.dir.createFile("seek.bin", .{ .read = true });2271 const file = try tmp.dir.createFile(io, "seek.bin", .{ .read = true });
2268 defer file.close();2272 defer file.close(io);
2269 {2273 {
2270 var buf: [16]u8 = undefined;2274 var buf: [16]u8 = undefined;
2271 var file_writer = std.fs.File.writer(file, &buf);2275 var file_writer = file.writer(io, &buf);
22722276
2273 try file_writer.interface.writeAll(contents);2277 try file_writer.interface.writeAll(contents);
2274 try file_writer.seekTo(8);2278 try file_writer.seekTo(8);
...@@ -2276,11 +2280,11 @@ test "seekTo flushes buffered data" {...@@ -2276,11 +2280,11 @@ test "seekTo flushes buffered data" {
2276 }2280 }
22772281
2278 var read_buffer: [16]u8 = undefined;2282 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
2281 var buf: [4]u8 = undefined;2285 var buf: [4]u8 = undefined;
2282 try file_reader.interface.readSliceAll(&buf);2286 try file_reader.interface.readSliceAll(&buf);
2283 try std.testing.expectEqualStrings(contents, &buf);2287 try expectEqualStrings(contents, &buf);
2284}2288}
22852289
2286test "File.Writer sendfile with buffered contents" {2290test "File.Writer sendfile with buffered contents" {
...@@ -2290,11 +2294,11 @@ test "File.Writer sendfile with buffered contents" {...@@ -2290,11 +2294,11 @@ test "File.Writer sendfile with buffered contents" {
2290 defer tmp_dir.cleanup();2294 defer tmp_dir.cleanup();
22912295
2292 {2296 {
2293 try tmp_dir.dir.writeFile(.{ .sub_path = "a", .data = "bcd" });2297 try tmp_dir.dir.writeFile(io, .{ .sub_path = "a", .data = "bcd" });
2294 const in = try tmp_dir.dir.openFile("a", .{});2298 const in = try tmp_dir.dir.openFile(io, "a", .{});
2295 defer in.close();2299 defer in.close(io);
2296 const out = try tmp_dir.dir.createFile("b", .{});2300 const out = try tmp_dir.dir.createFile(io, "b", .{});
2297 defer out.close();2301 defer out.close(io);
22982302
2299 var in_buf: [2]u8 = undefined;2303 var in_buf: [2]u8 = undefined;
2300 var in_r = in.reader(io, &in_buf);2304 var in_r = in.reader(io, &in_buf);
...@@ -2302,16 +2306,332 @@ test "File.Writer sendfile with buffered contents" {...@@ -2302,16 +2306,332 @@ test "File.Writer sendfile with buffered contents" {
2302 try in_r.interface.fill(2);2306 try in_r.interface.fill(2);
23032307
2304 var out_buf: [1]u8 = undefined;2308 var out_buf: [1]u8 = undefined;
2305 var out_w = out.writerStreaming(&out_buf);2309 var out_w = out.writerStreaming(io, &out_buf);
2306 try out_w.interface.writeByte('a');2310 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));
2308 try out_w.interface.flush();2312 try out_w.interface.flush();
2309 }2313 }
23102314
2311 var check = try tmp_dir.dir.openFile("b", .{});2315 var check = try tmp_dir.dir.openFile(io, "b", .{});
2312 defer check.close();2316 defer check.close(io);
2313 var check_buf: [4]u8 = undefined;2317 var check_buf: [4]u8 = undefined;
2314 var check_r = check.reader(io, &check_buf);2318 var check_r = check.reader(io, &check_buf);
2315 try testing.expectEqualStrings("abcd", try check_r.interface.take(4));2319 try expectEqualStrings("abcd", try check_r.interface.take(4));
2316 try testing.expectError(error.EndOfStream, check_r.interface.takeByte());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);
2317}2637}
lib/std/hash/benchmark.zig+3-2
...@@ -1,7 +1,8 @@...@@ -1,7 +1,8 @@
1// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig1// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig
2const builtin = @import("builtin");
23
3const std = @import("std");4const std = @import("std");
4const builtin = @import("builtin");5const Io = std.Io;
5const time = std.time;6const time = std.time;
6const Timer = time.Timer;7const Timer = time.Timer;
7const hash = std.hash;8const hash = std.hash;
...@@ -354,7 +355,7 @@ fn mode(comptime x: comptime_int) comptime_int {...@@ -354,7 +355,7 @@ fn mode(comptime x: comptime_int) comptime_int {
354355
355pub fn main() !void {356pub fn main() !void {
356 var stdout_buffer: [0x100]u8 = undefined;357 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);
358 const stdout = &stdout_writer.interface;359 const stdout = &stdout_writer.interface;
359360
360 var buffer: [1024]u8 = undefined;361 var buffer: [1024]u8 = undefined;
lib/std/heap/debug_allocator.zig+26-29
...@@ -84,7 +84,7 @@ const builtin = @import("builtin");...@@ -84,7 +84,7 @@ const builtin = @import("builtin");
84const StackTrace = std.builtin.StackTrace;84const StackTrace = std.builtin.StackTrace;
8585
86const std = @import("std");86const std = @import("std");
87const log = std.log.scoped(.gpa);87const log = std.log.scoped(.DebugAllocator);
88const math = std.math;88const math = std.math;
89const assert = std.debug.assert;89const assert = std.debug.assert;
90const mem = std.mem;90const mem = std.mem;
...@@ -425,7 +425,6 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -425,7 +425,6 @@ pub fn DebugAllocator(comptime config: Config) type {
425 bucket: *BucketHeader,425 bucket: *BucketHeader,
426 size_class_index: usize,426 size_class_index: usize,
427 used_bits_count: usize,427 used_bits_count: usize,
428 tty_config: std.Io.tty.Config,
429 ) usize {428 ) usize {
430 const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index));429 const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index));
431 const slot_count = slot_counts[size_class_index];430 const slot_count = slot_counts[size_class_index];
...@@ -445,7 +444,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -445,7 +444,7 @@ pub fn DebugAllocator(comptime config: Config) type {
445 addr,444 addr,
446 std.debug.FormatStackTrace{445 std.debug.FormatStackTrace{
447 .stack_trace = stack_trace,446 .stack_trace = stack_trace,
448 .tty_config = tty_config,447 .terminal_mode = std.log.terminalMode(),
449 },448 },
450 });449 });
451 leaks += 1;450 leaks += 1;
...@@ -460,14 +459,12 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -460,14 +459,12 @@ pub fn DebugAllocator(comptime config: Config) type {
460 pub fn detectLeaks(self: *Self) usize {459 pub fn detectLeaks(self: *Self) usize {
461 var leaks: usize = 0;460 var leaks: usize = 0;
462461
463 const tty_config: std.Io.tty.Config = .detect(.stderr());
464
465 for (self.buckets, 0..) |init_optional_bucket, size_class_index| {462 for (self.buckets, 0..) |init_optional_bucket, size_class_index| {
466 var optional_bucket = init_optional_bucket;463 var optional_bucket = init_optional_bucket;
467 const slot_count = slot_counts[size_class_index];464 const slot_count = slot_counts[size_class_index];
468 const used_bits_count = usedBitsCount(slot_count);465 const used_bits_count = usedBitsCount(slot_count);
469 while (optional_bucket) |bucket| {466 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);
471 optional_bucket = bucket.prev;468 optional_bucket = bucket.prev;
472 }469 }
473 }470 }
...@@ -480,7 +477,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -480,7 +477,7 @@ pub fn DebugAllocator(comptime config: Config) type {
480 @intFromPtr(large_alloc.bytes.ptr),477 @intFromPtr(large_alloc.bytes.ptr),
481 std.debug.FormatStackTrace{478 std.debug.FormatStackTrace{
482 .stack_trace = stack_trace,479 .stack_trace = stack_trace,
483 .tty_config = tty_config,480 .terminal_mode = std.log.terminalMode(),
484 },481 },
485 });482 });
486 leaks += 1;483 leaks += 1;
...@@ -534,21 +531,21 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -534,21 +531,21 @@ pub fn DebugAllocator(comptime config: Config) type {
534 }531 }
535532
536 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {533 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {
534 @branchHint(.cold);
537 var addr_buf: [stack_n]usize = undefined;535 var addr_buf: [stack_n]usize = undefined;
538 const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);536 const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
539 const tty_config: std.Io.tty.Config = .detect(.stderr());
540 log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{537 log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{
541 std.debug.FormatStackTrace{538 std.debug.FormatStackTrace{
542 .stack_trace = alloc_stack_trace,539 .stack_trace = alloc_stack_trace,
543 .tty_config = tty_config,540 .terminal_mode = std.log.terminalMode(),
544 },541 },
545 std.debug.FormatStackTrace{542 std.debug.FormatStackTrace{
546 .stack_trace = free_stack_trace,543 .stack_trace = free_stack_trace,
547 .tty_config = tty_config,544 .terminal_mode = std.log.terminalMode(),
548 },545 },
549 std.debug.FormatStackTrace{546 std.debug.FormatStackTrace{
550 .stack_trace = second_free_stack_trace,547 .stack_trace = second_free_stack_trace,
551 .tty_config = tty_config,548 .terminal_mode = std.log.terminalMode(),
552 },549 },
553 });550 });
554 }551 }
...@@ -588,19 +585,19 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -588,19 +585,19 @@ pub fn DebugAllocator(comptime config: Config) type {
588 }585 }
589586
590 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {587 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
588 @branchHint(.cold);
591 var addr_buf: [stack_n]usize = undefined;589 var addr_buf: [stack_n]usize = undefined;
592 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);590 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
593 const tty_config: std.Io.tty.Config = .detect(.stderr());
594 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{591 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
595 entry.value_ptr.bytes.len,592 entry.value_ptr.bytes.len,
596 old_mem.len,593 old_mem.len,
597 std.debug.FormatStackTrace{594 std.debug.FormatStackTrace{
598 .stack_trace = entry.value_ptr.getStackTrace(.alloc),595 .stack_trace = entry.value_ptr.getStackTrace(.alloc),
599 .tty_config = tty_config,596 .terminal_mode = std.log.terminalMode(),
600 },597 },
601 std.debug.FormatStackTrace{598 std.debug.FormatStackTrace{
602 .stack_trace = free_stack_trace,599 .stack_trace = free_stack_trace,
603 .tty_config = tty_config,600 .terminal_mode = std.log.terminalMode(),
604 },601 },
605 });602 });
606 }603 }
...@@ -701,19 +698,19 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -701,19 +698,19 @@ pub fn DebugAllocator(comptime config: Config) type {
701 }698 }
702699
703 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {700 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
701 @branchHint(.cold);
704 var addr_buf: [stack_n]usize = undefined;702 var addr_buf: [stack_n]usize = undefined;
705 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);703 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
706 const tty_config: std.Io.tty.Config = .detect(.stderr());
707 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{704 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
708 entry.value_ptr.bytes.len,705 entry.value_ptr.bytes.len,
709 old_mem.len,706 old_mem.len,
710 std.debug.FormatStackTrace{707 std.debug.FormatStackTrace{
711 .stack_trace = entry.value_ptr.getStackTrace(.alloc),708 .stack_trace = entry.value_ptr.getStackTrace(.alloc),
712 .tty_config = tty_config,709 .terminal_mode = std.log.terminalMode(),
713 },710 },
714 std.debug.FormatStackTrace{711 std.debug.FormatStackTrace{
715 .stack_trace = free_stack_trace,712 .stack_trace = free_stack_trace,
716 .tty_config = tty_config,713 .terminal_mode = std.log.terminalMode(),
717 },714 },
718 });715 });
719 }716 }
...@@ -935,32 +932,32 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -935,32 +932,32 @@ pub fn DebugAllocator(comptime config: Config) type {
935 var addr_buf: [stack_n]usize = undefined;932 var addr_buf: [stack_n]usize = undefined;
936 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);933 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
937 if (old_memory.len != requested_size) {934 if (old_memory.len != requested_size) {
938 const tty_config: std.Io.tty.Config = .detect(.stderr());935 @branchHint(.cold);
939 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{936 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
940 requested_size,937 requested_size,
941 old_memory.len,938 old_memory.len,
942 std.debug.FormatStackTrace{939 std.debug.FormatStackTrace{
943 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),940 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
944 .tty_config = tty_config,941 .terminal_mode = std.log.terminalMode(),
945 },942 },
946 std.debug.FormatStackTrace{943 std.debug.FormatStackTrace{
947 .stack_trace = free_stack_trace,944 .stack_trace = free_stack_trace,
948 .tty_config = tty_config,945 .terminal_mode = std.log.terminalMode(),
949 },946 },
950 });947 });
951 }948 }
952 if (alignment != slot_alignment) {949 if (alignment != slot_alignment) {
953 const tty_config: std.Io.tty.Config = .detect(.stderr());950 @branchHint(.cold);
954 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{951 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
955 slot_alignment.toByteUnits(),952 slot_alignment.toByteUnits(),
956 alignment.toByteUnits(),953 alignment.toByteUnits(),
957 std.debug.FormatStackTrace{954 std.debug.FormatStackTrace{
958 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),955 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
959 .tty_config = tty_config,956 .terminal_mode = std.log.terminalMode(),
960 },957 },
961 std.debug.FormatStackTrace{958 std.debug.FormatStackTrace{
962 .stack_trace = free_stack_trace,959 .stack_trace = free_stack_trace,
963 .tty_config = tty_config,960 .terminal_mode = std.log.terminalMode(),
964 },961 },
965 });962 });
966 }963 }
...@@ -1044,32 +1041,32 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -1044,32 +1041,32 @@ pub fn DebugAllocator(comptime config: Config) type {
1044 var addr_buf: [stack_n]usize = undefined;1041 var addr_buf: [stack_n]usize = undefined;
1045 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);1042 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
1046 if (memory.len != requested_size) {1043 if (memory.len != requested_size) {
1047 const tty_config: std.Io.tty.Config = .detect(.stderr());1044 @branchHint(.cold);
1048 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{1045 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
1049 requested_size,1046 requested_size,
1050 memory.len,1047 memory.len,
1051 std.debug.FormatStackTrace{1048 std.debug.FormatStackTrace{
1052 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),1049 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
1053 .tty_config = tty_config,1050 .terminal_mode = std.log.terminalMode(),
1054 },1051 },
1055 std.debug.FormatStackTrace{1052 std.debug.FormatStackTrace{
1056 .stack_trace = free_stack_trace,1053 .stack_trace = free_stack_trace,
1057 .tty_config = tty_config,1054 .terminal_mode = std.log.terminalMode(),
1058 },1055 },
1059 });1056 });
1060 }1057 }
1061 if (alignment != slot_alignment) {1058 if (alignment != slot_alignment) {
1062 const tty_config: std.Io.tty.Config = .detect(.stderr());1059 @branchHint(.cold);
1063 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{1060 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
1064 slot_alignment.toByteUnits(),1061 slot_alignment.toByteUnits(),
1065 alignment.toByteUnits(),1062 alignment.toByteUnits(),
1066 std.debug.FormatStackTrace{1063 std.debug.FormatStackTrace{
1067 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),1064 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
1068 .tty_config = tty_config,1065 .terminal_mode = std.log.terminalMode(),
1069 },1066 },
1070 std.debug.FormatStackTrace{1067 std.debug.FormatStackTrace{
1071 .stack_trace = free_stack_trace,1068 .stack_trace = free_stack_trace,
1072 .tty_config = tty_config,1069 .terminal_mode = std.log.terminalMode(),
1073 },1070 },
1074 });1071 });
1075 }1072 }
lib/std/http.zig+1-1
...@@ -2,7 +2,7 @@ const builtin = @import("builtin");...@@ -2,7 +2,7 @@ const builtin = @import("builtin");
2const std = @import("std.zig");2const std = @import("std.zig");
3const assert = std.debug.assert;3const assert = std.debug.assert;
4const Writer = std.Io.Writer;4const Writer = std.Io.Writer;
5const File = std.fs.File;5const File = std.Io.File;
66
7pub const Client = @import("http/Client.zig");7pub const Client = @import("http/Client.zig");
8pub const Server = @import("http/Server.zig");8pub 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...@@ -1473,6 +1473,8 @@ pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{N
1473///1473///
1474/// This function is threadsafe.1474/// This function is threadsafe.
1475pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connection {1475pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connection {
1476 const io = client.io;
1477
1476 if (client.connection_pool.findConnection(.{1478 if (client.connection_pool.findConnection(.{
1477 .host = path,1479 .host = path,
1478 .port = 0,1480 .port = 0,
...@@ -1485,7 +1487,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti...@@ -1485,7 +1487,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
1485 conn.* = .{ .data = undefined };1487 conn.* = .{ .data = undefined };
14861488
1487 const stream = try Io.net.connectUnixSocket(path);1489 const stream = try Io.net.connectUnixSocket(path);
1488 errdefer stream.close();1490 errdefer stream.close(io);
14891491
1490 conn.data = .{1492 conn.data = .{
1491 .stream = stream,1493 .stream = stream,
lib/std/json/dynamic.zig+3-4
...@@ -47,10 +47,9 @@ pub const Value = union(enum) {...@@ -47,10 +47,9 @@ pub const Value = union(enum) {
47 }47 }
4848
49 pub fn dump(v: Value) void {49 pub fn dump(v: Value) void {
50 const w, _ = std.debug.lockStderrWriter(&.{});50 const stderr = std.debug.lockStderr(&.{}, null);
51 defer std.debug.unlockStderrWriter();51 defer std.debug.unlockStderr();
5252 json.Stringify.value(v, .{}, &stderr.file_writer.interface) catch return;
53 json.Stringify.value(v, .{}, w) catch return;
54 }53 }
5554
56 pub fn jsonStringify(value: @This(), jws: anytype) !void {55 pub fn jsonStringify(value: @This(), jws: anytype) !void {
lib/std/log.zig+31-15
...@@ -15,7 +15,7 @@...@@ -15,7 +15,7 @@
15//!15//!
16//! For an example implementation of the `logFn` function, see `defaultLog`,16//! For an example implementation of the `logFn` function, see `defaultLog`,
17//! which is the default implementation. It outputs to stderr, using color if17//! 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:
19//! ```19//! ```
20//! error: this is an error20//! error: this is an error
21//! error(scope): this is an error with a non-default scope21//! 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 {...@@ -80,6 +80,14 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool {
80 return @intFromEnum(level) <= @intFromEnum(std.options.log_level);80 return @intFromEnum(level) <= @intFromEnum(std.options.log_level);
81}81}
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
83/// The default implementation for the log function. Custom log functions may91/// The default implementation for the log function. Custom log functions may
84/// forward log messages to this function.92/// forward log messages to this function.
85///93///
...@@ -92,25 +100,33 @@ pub fn defaultLog(...@@ -92,25 +100,33 @@ pub fn defaultLog(
92 args: anytype,100 args: anytype,
93) void {101) void {
94 var buffer: [64]u8 = undefined;102 var buffer: [64]u8 = undefined;
95 const stderr, const ttyconf = std.debug.lockStderrWriter(&buffer);103 const stderr = std.debug.lockStderr(&buffer).terminal();
96 defer std.debug.unlockStderrWriter();104 defer std.debug.unlockStderr();
97 ttyconf.setColor(stderr, switch (level) {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) {
98 .err => .red,116 .err => .red,
99 .warn => .yellow,117 .warn => .yellow,
100 .info => .green,118 .info => .green,
101 .debug => .magenta,119 .debug => .magenta,
102 }) catch {};120 }) catch {};
103 ttyconf.setColor(stderr, .bold) catch {};121 t.setColor(.bold) catch {};
104 stderr.writeAll(level.asText()) catch return;122 try t.writer.writeAll(level.asText());
105 ttyconf.setColor(stderr, .reset) catch {};123 t.setColor(.reset) catch {};
106 ttyconf.setColor(stderr, .dim) catch {};124 t.setColor(.dim) catch {};
107 ttyconf.setColor(stderr, .bold) catch {};125 t.setColor(.bold) catch {};
108 if (scope != .default) {126 if (scope != .default) try t.writer.print("({t})", .{scope});
109 stderr.print("({s})", .{@tagName(scope)}) catch return;127 try t.writer.writeAll(": ");
110 }128 t.setColor(.reset) catch {};
111 stderr.writeAll(": ") catch return;129 try t.writer.print(format ++ "\n", args);
112 ttyconf.setColor(stderr, .reset) catch {};
113 stderr.print(format ++ "\n", args) catch return;
114}130}
115131
116/// Returns a scoped logging namespace that logs all messages using the scope132/// 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;...@@ -21,7 +21,6 @@ const mem = std.mem;
21const elf = std.elf;21const elf = std.elf;
22const fs = std.fs;22const fs = std.fs;
23const dl = @import("dynamic_library.zig");23const dl = @import("dynamic_library.zig");
24const max_path_bytes = std.fs.max_path_bytes;
25const posix = std.posix;24const posix = std.posix;
26const native_os = builtin.os.tag;25const native_os = builtin.os.tag;
2726
...@@ -31,7 +30,6 @@ pub const uefi = @import("os/uefi.zig");...@@ -31,7 +30,6 @@ pub const uefi = @import("os/uefi.zig");
31pub const wasi = @import("os/wasi.zig");30pub const wasi = @import("os/wasi.zig");
32pub const emscripten = @import("os/emscripten.zig");31pub const emscripten = @import("os/emscripten.zig");
33pub const windows = @import("os/windows.zig");32pub const windows = @import("os/windows.zig");
34pub const freebsd = @import("os/freebsd.zig");
3533
36test {34test {
37 _ = linux;35 _ = linux;
...@@ -56,135 +54,6 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o...@@ -56,135 +54,6 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o
56 else => undefined,54 else => undefined,
57};55};
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
188pub const FstatError = error{57pub const FstatError = error{
189 SystemResources,58 SystemResources,
190 AccessDenied,59 AccessDenied,
...@@ -203,3 +72,8 @@ pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t {...@@ -203,3 +72,8 @@ pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t {
203 else => |err| return posix.unexpectedErrno(err),72 else => |err| return posix.unexpectedErrno(err),
204 }73 }
205}74}
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 {...@@ -1420,7 +1420,7 @@ pub fn chmod(path: [*:0]const u8, mode: mode_t) usize {
1420 if (@hasField(SYS, "chmod")) {1420 if (@hasField(SYS, "chmod")) {
1421 return syscall2(.chmod, @intFromPtr(path), mode);1421 return syscall2(.chmod, @intFromPtr(path), mode);
1422 } else {1422 } else {
1423 return fchmodat(AT.FDCWD, path, mode, 0);1423 return fchmodat(AT.FDCWD, path, mode);
1424 }1424 }
1425}1425}
14261426
...@@ -1432,7 +1432,7 @@ pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize {...@@ -1432,7 +1432,7 @@ pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize {
1432 }1432 }
1433}1433}
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 {
1436 return syscall3(.fchmodat, @bitCast(@as(isize, fd)), @intFromPtr(path), mode);1436 return syscall3(.fchmodat, @bitCast(@as(isize, fd)), @intFromPtr(path), mode);
1437}1437}
14381438
...@@ -1561,14 +1561,14 @@ pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize {...@@ -1561,14 +1561,14 @@ pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize {
1561 }1561 }
1562}1562}
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 {
1565 return syscall5(1565 return syscall5(
1566 .linkat,1566 .linkat,
1567 @as(usize, @bitCast(@as(isize, oldfd))),1567 @as(usize, @bitCast(@as(isize, oldfd))),
1568 @intFromPtr(oldpath),1568 @intFromPtr(oldpath),
1569 @as(usize, @bitCast(@as(isize, newfd))),1569 @as(usize, @bitCast(@as(isize, newfd))),
1570 @intFromPtr(newpath),1570 @intFromPtr(newpath),
1571 @as(usize, @bitCast(@as(isize, flags))),1571 flags,
1572 );1572 );
1573}1573}
15741574
...@@ -6040,7 +6040,7 @@ pub const dirent64 = extern struct {...@@ -6040,7 +6040,7 @@ pub const dirent64 = extern struct {
6040 off: u64,6040 off: u64,
6041 reclen: u16,6041 reclen: u16,
6042 type: u8,6042 type: u8,
6043 name: u8, // field address is the address of first byte of name https://github.com/ziglang/zig/issues/1736043 name: [0]u8,
6044};6044};
60456045
6046pub const dl_phdr_info = extern struct {6046pub const dl_phdr_info = extern struct {
...@@ -6891,10 +6891,6 @@ pub const utsname = extern struct {...@@ -6891,10 +6891,6 @@ pub const utsname = extern struct {
6891};6891};
6892pub const HOST_NAME_MAX = 64;6892pub 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.
6898pub const STATX = packed struct(u32) {6894pub const STATX = packed struct(u32) {
6899 /// Want `mode & S.IFMT`.6895 /// Want `mode & S.IFMT`.
6900 TYPE: bool = false,6896 TYPE: bool = false,
...@@ -6982,7 +6978,9 @@ pub const statx_timestamp = extern struct {...@@ -6982,7 +6978,9 @@ pub const statx_timestamp = extern struct {
69826978
6983/// Renamed to `Statx` to not conflict with the `statx` function.6979/// Renamed to `Statx` to not conflict with the `statx` function.
6984pub const Statx = extern struct {6980pub 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.
6986 mask: STATX,6984 mask: STATX,
6987 /// Block size for filesystem I/O.6985 /// Block size for filesystem I/O.
6988 blksize: u32,6986 blksize: u32,
...@@ -9872,165 +9870,3 @@ pub const cmsghdr = extern struct {...@@ -9872,165 +9870,3 @@ pub const cmsghdr = extern struct {
9872 level: i32,9870 level: i32,
9873 type: i32,9871 type: i32,
9874};9872};
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 @@...@@ -1,14 +1,17 @@
1const IoUring = @This();1const IoUring = @This();
2const std = @import("std");2
3const builtin = @import("builtin");3const 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;
4const assert = std.debug.assert;9const assert = std.debug.assert;
5const mem = std.mem;
6const net = std.Io.net;
7const posix = std.posix;10const posix = std.posix;
8const linux = std.os.linux;11const linux = std.os.linux;
9const testing = std.testing;12const testing = std.testing;
10const is_linux = builtin.os.tag == .linux;
11const page_size_min = std.heap.page_size_min;13const page_size_min = std.heap.page_size_min;
14const createSocketTestHarness = @import("IoUring/test.zig").createSocketTestHarness;
1215
13fd: linux.fd_t = -1,16fd: linux.fd_t = -1,
14sq: SubmissionQueue,17sq: SubmissionQueue,
...@@ -22,7 +25,7 @@ features: u32,...@@ -22,7 +25,7 @@ features: u32,
22/// see https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L8027-L8050.25/// see https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L8027-L8050.
23/// Matches the interface of io_uring_queue_init() in liburing.26/// Matches the interface of io_uring_queue_init() in liburing.
24pub fn init(entries: u16, flags: u32) !IoUring {27pub 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, .{
26 .flags = flags,29 .flags = flags,
27 .sq_thread_idle = 1000,30 .sq_thread_idle = 1000,
28 });31 });
...@@ -1309,7 +1312,7 @@ pub fn unregister_buffers(self: *IoUring) !void {...@@ -1309,7 +1312,7 @@ pub fn unregister_buffers(self: *IoUring) !void {
1309/// io_uring subsystem of the running kernel. The io_uring_probe contains the1312/// io_uring subsystem of the running kernel. The io_uring_probe contains the
1310/// list of supported operations.1313/// list of supported operations.
1311pub fn get_probe(self: *IoUring) !linux.io_uring_probe {1314pub 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, .{});
1313 const res = linux.io_uring_register(self.fd, .REGISTER_PROBE, &probe, probe.ops.len);1316 const res = linux.io_uring_register(self.fd, .REGISTER_PROBE, &probe, probe.ops.len);
1314 try handle_register_buf_ring_result(res);1317 try handle_register_buf_ring_result(res);
1315 return probe;1318 return probe;
...@@ -1636,7 +1639,7 @@ pub const BufferGroup = struct {...@@ -1636,7 +1639,7 @@ pub const BufferGroup = struct {
16361639
1637 pub fn init(1640 pub fn init(
1638 ring: *IoUring,1641 ring: *IoUring,
1639 allocator: mem.Allocator,1642 allocator: Allocator,
1640 group_id: u16,1643 group_id: u16,
1641 buffer_size: u32,1644 buffer_size: u32,
1642 buffers_count: u16,1645 buffers_count: u16,
...@@ -1670,7 +1673,7 @@ pub const BufferGroup = struct {...@@ -1670,7 +1673,7 @@ pub const BufferGroup = struct {
1670 };1673 };
1671 }1674 }
16721675
1673 pub fn deinit(self: *BufferGroup, allocator: mem.Allocator) void {1676 pub fn deinit(self: *BufferGroup, allocator: Allocator) void {
1674 free_buf_ring(self.ring.fd, self.br, self.buffers_count, self.group_id);1677 free_buf_ring(self.ring.fd, self.br, self.buffers_count, self.group_id);
1675 allocator.free(self.buffers);1678 allocator.free(self.buffers);
1676 allocator.free(self.heads);1679 allocator.free(self.heads);
...@@ -1695,7 +1698,7 @@ pub const BufferGroup = struct {...@@ -1695,7 +1698,7 @@ pub const BufferGroup = struct {
1695 }1698 }
16961699
1697 // Get buffer by id.1700 // 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 {
1699 const pos = self.buffer_size * buffer_id;1702 const pos = self.buffer_size * buffer_id;
1700 return self.buffers[pos .. pos + self.buffer_size][self.heads[buffer_id]..];1703 return self.buffers[pos .. pos + self.buffer_size][self.heads[buffer_id]..];
1701 }1704 }
...@@ -1764,7 +1767,7 @@ fn register_buf_ring(...@@ -1764,7 +1767,7 @@ fn register_buf_ring(
1764 group_id: u16,1767 group_id: u16,
1765 flags: linux.io_uring_buf_reg.Flags,1768 flags: linux.io_uring_buf_reg.Flags,
1766) !void {1769) !void {
1767 var reg = mem.zeroInit(linux.io_uring_buf_reg, .{1770 var reg = std.mem.zeroInit(linux.io_uring_buf_reg, .{
1768 .ring_addr = addr,1771 .ring_addr = addr,
1769 .ring_entries = entries,1772 .ring_entries = entries,
1770 .bgid = group_id,1773 .bgid = group_id,
...@@ -1781,7 +1784,7 @@ fn register_buf_ring(...@@ -1781,7 +1784,7 @@ fn register_buf_ring(
1781}1784}
17821785
1783fn unregister_buf_ring(fd: linux.fd_t, group_id: u16) !void {1786fn 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, .{
1785 .bgid = group_id,1788 .bgid = group_id,
1786 });1789 });
1787 const res = linux.io_uring_register(1790 const res = linux.io_uring_register(
...@@ -1848,731 +1851,13 @@ pub fn buf_ring_advance(br: *linux.io_uring_buf_ring, count: u16) void {...@@ -1848,731 +1851,13 @@ pub fn buf_ring_advance(br: *linux.io_uring_buf_ring, count: u16) void {
1848 @atomicStore(u16, &br.tail, tail, .release);1851 @atomicStore(u16, &br.tail, tail, .release);
1849}1852}
18501853
1851test "structs/offsets/entries" {1854test BufferGroup {
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)" {
2447 if (!is_linux) return error.SkipZigTest;1855 if (!is_linux) return error.SkipZigTest;
24481856
2449 const io = testing.io;1857 const io = testing.io;
1858 _ = io;
24501859
2451 var ring = IoUring.init(1, 0) catch |err| switch (err) {1860 // Init IoUring
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
2576 var ring = IoUring.init(16, 0) catch |err| switch (err) {1861 var ring = IoUring.init(16, 0) catch |err| switch (err) {
2577 error.SystemOutdated => return error.SkipZigTest,1862 error.SystemOutdated => return error.SkipZigTest,
2578 error.PermissionDenied => return error.SkipZigTest,1863 error.PermissionDenied => return error.SkipZigTest,
...@@ -2580,2052 +1865,60 @@ test "accept/connect/recv/link_timeout" {...@@ -2580,2052 +1865,60 @@ test "accept/connect/recv/link_timeout" {
2580 };1865 };
2581 defer ring.deinit();1866 defer ring.deinit();
25821867
2583 const socket_test_harness = try createSocketTestHarness(&ring);1868 // Init buffer group for ring
2584 defer socket_test_harness.close();1869 const group_id: u16 = 1; // buffers group id
25851870 const buffers_count: u16 = 1; // number of buffers in buffer group
2586 var buffer_recv = [_]u8{ 0, 1, 0, 1, 0 };1871 const buffer_size: usize = 128; // size of each buffer in group
25871872 var buf_grp = BufferGroup.init(
2588 const sqe_recv = try ring.recv(0xffffffff, socket_test_harness.server, .{ .buffer = buffer_recv[0..] }, 0);1873 &ring,
2589 sqe_recv.flags |= linux.IOSQE_IO_LINK;1874 testing.allocator,
25901875 group_id,
2591 const ts = linux.kernel_timespec{ .sec = 0, .nsec = 1000000 };1876 buffer_size,
2592 _ = try ring.link_timeout(0x22222222, &ts, 0);1877 buffers_count,
25931878 ) catch |err| switch (err) {
2594 const nr_wait = try ring.submit();1879 // kernel older than 5.19
2595 try testing.expectEqual(@as(u32, 2), nr_wait);1880 error.ArgumentsInvalid => return error.SkipZigTest,
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,
2628 else => return err,1881 else => return err,
2629 };1882 };
2630 defer ring.deinit();1883 defer buf_grp.deinit(testing.allocator);
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);
26401884
2641 const len: u64 = 65536;1885 // Create client/server fds
2642 const sqe = try ring.fallocate(0xaaaaaaaa, file.handle, 0, 0, len);1886 const fds = try createSocketTestHarness(&ring);
2643 try testing.expectEqual(linux.IORING_OP.FALLOCATE, sqe.opcode);1887 defer fds.close();
2644 try testing.expectEqual(file.handle, sqe.fd);1888 const data = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe };
2645 try testing.expectEqual(@as(u32, 1), try ring.submit());
26461889
2647 const cqe = try ring.copy_cqe();1890 // Client sends data
2648 switch (cqe.err()) {1891 {
2649 .SUCCESS => {},1892 _ = try ring.send(1, fds.client, data[0..], 0);
2650 // This kernel's io_uring does not yet implement fallocate():1893 const submitted = try ring.submit();
2651 .INVAL => return error.SkipZigTest,1894 try testing.expectEqual(1, submitted);
2652 // This kernel does not implement fallocate():1895 const cqe_send = try ring.copy_cqe();
2653 .NOSYS => return error.SkipZigTest,1896 if (cqe_send.err() == .INVAL) return error.SkipZigTest;
2654 // The filesystem containing the file referred to by fd does not support this operation;1897 try testing.expectEqual(linux.io_uring_cqe{ .user_data = 1, .res = data.len, .flags = 0 }, cqe_send);
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}),
2658 }1898 }
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;1907 // ... when we have completion for recv operation
2689 const sqe = try ring.statx(1908 const cqe = try ring.copy_cqe();
2690 0xaaaaaaaa,1909 try testing.expectEqual(2, cqe.user_data); // matches submitted user_data
2691 tmp.dir.fd,1910 try testing.expect(cqe.res >= 0); // success
2692 path,1911 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
2693 0,1912 try testing.expectEqual(data.len, @as(usize, @intCast(cqe.res))); // cqe.res holds received data len
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());
27001913
2701 const cqe = try ring.copy_cqe();1914 // Get buffer from pool
2702 switch (cqe.err()) {1915 const buf = try buf_grp.get(cqe);
2703 .SUCCESS => {},1916 try testing.expectEqualSlices(u8, &data, buf);
2704 // This kernel's io_uring does not yet implement statx():1917 // Release buffer to the kernel when application is done with it
2705 .INVAL => return error.SkipZigTest,1918 try buf_grp.put(cqe);
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}),
2714 }1919 }
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..]);
4627}1920}
46281921
4629fn addrAny(addr: *linux.sockaddr.in) *linux.sockaddr {1922test {
4630 return @ptrCast(addr);1923 if (is_linux) _ = @import("IoUring/test.zig");
4631}1924}
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;...@@ -12,14 +12,16 @@ const fs = std.fs;
12test "fallocate" {12test "fallocate" {
13 if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/3022013 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
15 var tmp = std.testing.tmpDir(.{});17 var tmp = std.testing.tmpDir(.{});
16 defer tmp.cleanup();18 defer tmp.cleanup();
1719
18 const path = "test_fallocate";20 const path = "test_fallocate";
19 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });21 const file = try tmp.dir.createFile(io, path, .{ .truncate = true, .permissions = .fromMode(0o666) });
20 defer file.close();22 defer file.close(io);
2123
22 try expect((try file.stat()).size == 0);24 try expect((try file.stat(io)).size == 0);
2325
24 const len: i64 = 65536;26 const len: i64 = 65536;
25 switch (linux.errno(linux.fallocate(file.handle, 0, 0, len))) {27 switch (linux.errno(linux.fallocate(file.handle, 0, 0, len))) {
...@@ -29,7 +31,7 @@ test "fallocate" {...@@ -29,7 +31,7 @@ test "fallocate" {
29 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),31 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
30 }32 }
3133
32 try expect((try file.stat()).size == len);34 try expect((try file.stat(io)).size == len);
33}35}
3436
35test "getpid" {37test "getpid" {
...@@ -77,12 +79,14 @@ test "timer" {...@@ -77,12 +79,14 @@ test "timer" {
77}79}
7880
79test "statx" {81test "statx" {
82 const io = std.testing.io;
83
80 var tmp = std.testing.tmpDir(.{});84 var tmp = std.testing.tmpDir(.{});
81 defer tmp.cleanup();85 defer tmp.cleanup();
8286
83 const tmp_file_name = "just_a_temporary_file.txt";87 const tmp_file_name = "just_a_temporary_file.txt";
84 var file = try tmp.dir.createFile(tmp_file_name, .{});88 var file = try tmp.dir.createFile(io, tmp_file_name, .{});
85 defer file.close();89 defer file.close(io);
8690
87 var buf: linux.Statx = undefined;91 var buf: linux.Statx = undefined;
88 switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .BASIC_STATS, &buf))) {92 switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .BASIC_STATS, &buf))) {
...@@ -111,15 +115,17 @@ test "user and group ids" {...@@ -111,15 +115,17 @@ test "user and group ids" {
111}115}
112116
113test "fadvise" {117test "fadvise" {
118 const io = std.testing.io;
119
114 var tmp = std.testing.tmpDir(.{});120 var tmp = std.testing.tmpDir(.{});
115 defer tmp.cleanup();121 defer tmp.cleanup();
116122
117 const tmp_file_name = "temp_posix_fadvise.txt";123 const tmp_file_name = "temp_posix_fadvise.txt";
118 var file = try tmp.dir.createFile(tmp_file_name, .{});124 var file = try tmp.dir.createFile(io, tmp_file_name, .{});
119 defer file.close();125 defer file.close(io);
120126
121 var buf: [2048]u8 = undefined;127 var buf: [2048]u8 = undefined;
122 try file.writeAll(&buf);128 try file.writeStreamingAll(io, &buf);
123129
124 const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL);130 const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL);
125 try expectEqual(@as(usize, 0), ret);131 try expectEqual(@as(usize, 0), ret);
...@@ -401,14 +407,6 @@ test "futex2_requeue" {...@@ -401,14 +407,6 @@ test "futex2_requeue" {
401 try expectEqual(0, rc);407 try expectEqual(0, rc);
402}408}
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
412test {410test {
413 _ = linux.IoUring;411 _ = linux.IoUring;
414}412}
lib/std/os/uefi/protocol/file.zig-9
...@@ -163,15 +163,6 @@ pub const File = extern struct {...@@ -163,15 +163,6 @@ pub const File = extern struct {
163 }163 }
164 }164 }
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
175 pub fn setPosition(self: *File, position: u64) SeekError!void {166 pub fn setPosition(self: *File, position: u64) SeekError!void {
176 switch (self._set_position(self, position)) {167 switch (self._set_position(self, position)) {
177 .success => {},168 .success => {},
lib/std/os/windows.zig+20-237
...@@ -215,6 +215,10 @@ pub const FILE = struct {...@@ -215,6 +215,10 @@ pub const FILE = struct {
215 AccessFlags: ACCESS_MASK,215 AccessFlags: ACCESS_MASK,
216 };216 };
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.
218 pub const RENAME_INFORMATION = extern struct {222 pub const RENAME_INFORMATION = extern struct {
219 Flags: FLAGS,223 Flags: FLAGS,
220 RootDirectory: ?HANDLE,224 RootDirectory: ?HANDLE,
...@@ -2310,17 +2314,15 @@ pub const OpenFileOptions = struct {...@@ -2310,17 +2314,15 @@ pub const OpenFileOptions = struct {
2310 sa: ?*SECURITY_ATTRIBUTES = null,2314 sa: ?*SECURITY_ATTRIBUTES = null,
2311 share_access: FILE.SHARE = .VALID_FLAGS,2315 share_access: FILE.SHARE = .VALID_FLAGS,
2312 creation: FILE.CREATE_DISPOSITION,2316 creation: FILE.CREATE_DISPOSITION,
2313 /// If true, tries to open path as a directory.2317 filter: Filter = .non_directory_only,
2314 /// Defaults to false.
2315 filter: Filter = .file_only,
2316 /// If false, tries to open path as a reparse point without dereferencing it.2318 /// If false, tries to open path as a reparse point without dereferencing it.
2317 /// Defaults to true.2319 /// Defaults to true.
2318 follow_symlinks: bool = true,2320 follow_symlinks: bool = true,
23192321
2320 pub const Filter = enum {2322 pub const Filter = enum {
2321 /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory.2323 /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory.
2322 file_only,2324 non_directory_only,
2323 /// Causes `OpenFile` to return `error.NotDir` if the opened handle would be a file.2325 /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory.
2324 dir_only,2326 dir_only,
2325 /// `OpenFile` does not discriminate between opening files and directories.2327 /// `OpenFile` does not discriminate between opening files and directories.
2326 any,2328 any,
...@@ -2328,10 +2330,10 @@ pub const OpenFileOptions = struct {...@@ -2328,10 +2330,10 @@ pub const OpenFileOptions = struct {
2328};2330};
23292331
2330pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {2332pub 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) {
2332 return error.IsDir;2334 return error.IsDir;
2333 }2335 }
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) {
2335 return error.IsDir;2337 return error.IsDir;
2336 }2338 }
23372339
...@@ -2366,7 +2368,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN...@@ -2366,7 +2368,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
2366 options.creation,2368 options.creation,
2367 .{2369 .{
2368 .DIRECTORY_FILE = options.filter == .dir_only,2370 .DIRECTORY_FILE = options.filter == .dir_only,
2369 .NON_DIRECTORY_FILE = options.filter == .file_only,2371 .NON_DIRECTORY_FILE = options.filter == .non_directory_only,
2370 .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS,2372 .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS,
2371 .OPEN_REPARSE_POINT = !options.follow_symlinks,2373 .OPEN_REPARSE_POINT = !options.follow_symlinks,
2372 },2374 },
...@@ -2828,149 +2830,6 @@ pub fn CloseHandle(hObject: HANDLE) void {...@@ -2828,149 +2830,6 @@ pub fn CloseHandle(hObject: HANDLE) void {
2828 assert(ntdll.NtClose(hObject) == .SUCCESS);2830 assert(ntdll.NtClose(hObject) == .SUCCESS);
2829}2831}
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
2974pub const GetCurrentDirectoryError = error{2833pub const GetCurrentDirectoryError = error{
2975 NameTooLong,2834 NameTooLong,
2976 Unexpected,2835 Unexpected,
...@@ -3040,7 +2899,7 @@ pub fn CreateSymbolicLink(...@@ -3040,7 +2899,7 @@ pub fn CreateSymbolicLink(
3040 },2899 },
3041 .dir = dir,2900 .dir = dir,
3042 .creation = .CREATE,2901 .creation = .CREATE,
3043 .filter = if (is_directory) .dir_only else .file_only,2902 .filter = if (is_directory) .dir_only else .non_directory_only,
3044 }) catch |err| switch (err) {2903 }) catch |err| switch (err) {
3045 error.IsDir => return error.PathAlreadyExists,2904 error.IsDir => return error.PathAlreadyExists,
3046 error.NotDir => return error.Unexpected,2905 error.NotDir => return error.Unexpected,
...@@ -3584,7 +3443,7 @@ test QueryObjectName {...@@ -3584,7 +3443,7 @@ test QueryObjectName {
3584 //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths.3443 //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths.
3585 var tmp = std.testing.tmpDir(.{});3444 var tmp = std.testing.tmpDir(.{});
3586 defer tmp.cleanup();3445 defer tmp.cleanup();
3587 const handle = tmp.dir.fd;3446 const handle = tmp.dir.handle;
3588 var out_buffer: [PATH_MAX_WIDE]u16 = undefined;3447 var out_buffer: [PATH_MAX_WIDE]u16 = undefined;
35893448
3590 const result_path = try QueryObjectName(handle, &out_buffer);3449 const result_path = try QueryObjectName(handle, &out_buffer);
...@@ -3597,7 +3456,6 @@ test QueryObjectName {...@@ -3597,7 +3456,6 @@ test QueryObjectName {
35973456
3598pub const GetFinalPathNameByHandleError = error{3457pub const GetFinalPathNameByHandleError = error{
3599 AccessDenied,3458 AccessDenied,
3600 BadPathName,
3601 FileNotFound,3459 FileNotFound,
3602 NameTooLong,3460 NameTooLong,
3603 /// The volume does not contain a recognized file system. File system3461 /// The volume does not contain a recognized file system. File system
...@@ -3622,6 +3480,8 @@ pub const GetFinalPathNameByHandleFormat = struct {...@@ -3622,6 +3480,8 @@ pub const GetFinalPathNameByHandleFormat = struct {
3622/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`).3480/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`).
3623/// If DOS volume name format is selected, note that this function does *not* prepend3481/// If DOS volume name format is selected, note that this function does *not* prepend
3624/// `\\?\` prefix to the resultant path.3482/// `\\?\` prefix to the resultant path.
3483///
3484/// TODO move this function into std.Io.Threaded and add cancelation checks
3625pub fn GetFinalPathNameByHandle(3485pub fn GetFinalPathNameByHandle(
3626 hFile: HANDLE,3486 hFile: HANDLE,
3627 fmt: GetFinalPathNameByHandleFormat,3487 fmt: GetFinalPathNameByHandleFormat,
...@@ -3701,6 +3561,7 @@ pub fn GetFinalPathNameByHandle(...@@ -3701,6 +3561,7 @@ pub fn GetFinalPathNameByHandle(
3701 error.WouldBlock => return error.Unexpected,3561 error.WouldBlock => return error.Unexpected,
3702 error.NetworkNotFound => return error.Unexpected,3562 error.NetworkNotFound => return error.Unexpected,
3703 error.AntivirusInterference => return error.Unexpected,3563 error.AntivirusInterference => return error.Unexpected,
3564 error.BadPathName => return error.Unexpected,
3704 else => |e| return e,3565 else => |e| return e,
3705 };3566 };
3706 defer CloseHandle(mgmt_handle);3567 defer CloseHandle(mgmt_handle);
...@@ -3746,9 +3607,7 @@ pub fn GetFinalPathNameByHandle(...@@ -3746,9 +3607,7 @@ pub fn GetFinalPathNameByHandle(
3746 const total_len = drive_letter.len + file_name_u16.len;3607 const total_len = drive_letter.len + file_name_u16.len;
37473608
3748 // Validate that DOS does not contain any spurious nul bytes.3609 // Validate that DOS does not contain any spurious nul bytes.
3749 if (mem.findScalar(u16, out_buffer[0..total_len], 0)) |_| {3610 assert(mem.findScalar(u16, out_buffer[0..total_len], 0) == null);
3750 return error.BadPathName;
3751 }
37523611
3753 return out_buffer[0..total_len];3612 return out_buffer[0..total_len];
3754 } else if (mountmgrIsVolumeName(symlink)) {3613 } else if (mountmgrIsVolumeName(symlink)) {
...@@ -3798,9 +3657,7 @@ pub fn GetFinalPathNameByHandle(...@@ -3798,9 +3657,7 @@ pub fn GetFinalPathNameByHandle(
3798 const total_len = volume_path.len + file_name_u16.len;3657 const total_len = volume_path.len + file_name_u16.len;
37993658
3800 // Validate that DOS does not contain any spurious nul bytes.3659 // Validate that DOS does not contain any spurious nul bytes.
3801 if (mem.findScalar(u16, out_buffer[0..total_len], 0)) |_| {3660 assert(mem.findScalar(u16, out_buffer[0..total_len], 0) == null);
3802 return error.BadPathName;
3803 }
38043661
3805 return out_buffer[0..total_len];3662 return out_buffer[0..total_len];
3806 }3663 }
...@@ -3847,7 +3704,7 @@ test GetFinalPathNameByHandle {...@@ -3847,7 +3704,7 @@ test GetFinalPathNameByHandle {
3847 //any file will do3704 //any file will do
3848 var tmp = std.testing.tmpDir(.{});3705 var tmp = std.testing.tmpDir(.{});
3849 defer tmp.cleanup();3706 defer tmp.cleanup();
3850 const handle = tmp.dir.fd;3707 const handle = tmp.dir.handle;
3851 var buffer: [PATH_MAX_WIDE]u16 = undefined;3708 var buffer: [PATH_MAX_WIDE]u16 = undefined;
38523709
3853 //check with sufficient size3710 //check with sufficient size
...@@ -4248,80 +4105,6 @@ pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter...@@ -4248,80 +4105,6 @@ pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter
4248 assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0);4105 assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0);
4249}4106}
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
4325/// This is a workaround for the C backend until zig has the ability to put4108/// This is a workaround for the C backend until zig has the ability to put
4326/// C code in inline assembly.4109/// C code in inline assembly.
4327extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque;4110extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque;
...@@ -4713,8 +4496,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE...@@ -4713,8 +4496,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
4713 break :path_to_get path;4496 break :path_to_get path;
4714 }4497 }
4715 // We can also skip GetFinalPathNameByHandle if the handle matches4498 // We can also skip GetFinalPathNameByHandle if the handle matches
4716 // the handle returned by fs.cwd()4499 // the handle returned by Io.Dir.cwd()
4717 if (dir.? == std.fs.cwd().fd) {4500 if (dir.? == Io.Dir.cwd().handle) {
4718 break :path_to_get path;4501 break :path_to_get path;
4719 }4502 }
4720 // At this point, we know we have a relative path that had too many4503 // 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;...@@ -12,7 +12,7 @@ const math = std.math;
12const mem = std.mem;12const mem = std.mem;
13const coff = std.coff;13const coff = std.coff;
14const fs = std.fs;14const fs = std.fs;
15const File = std.fs.File;15const File = std.Io.File;
16const debug = std.debug;16const debug = std.debug;
1717
18const ArrayList = std.ArrayList;18const ArrayList = std.ArrayList;
lib/std/posix.zig+404-2954
...@@ -15,15 +15,16 @@...@@ -15,15 +15,16 @@
15//! deal with the exception.15//! deal with the exception.
1616
17const builtin = @import("builtin");17const builtin = @import("builtin");
18const root = @import("root");18const native_os = builtin.os.tag;
19
19const std = @import("std.zig");20const std = @import("std.zig");
21const Io = std.Io;
20const mem = std.mem;22const mem = std.mem;
21const fs = std.fs;23const fs = std.fs;
22const max_path_bytes = fs.max_path_bytes;24const max_path_bytes = std.fs.max_path_bytes;
23const maxInt = std.math.maxInt;25const maxInt = std.math.maxInt;
24const cast = std.math.cast;26const cast = std.math.cast;
25const assert = std.debug.assert;27const assert = std.debug.assert;
26const native_os = builtin.os.tag;
27const page_size_min = std.heap.page_size_min;28const page_size_min = std.heap.page_size_min;
2829
29test {30test {
...@@ -53,6 +54,7 @@ else switch (native_os) {...@@ -53,6 +54,7 @@ else switch (native_os) {
53 pub const uid_t = void;54 pub const uid_t = void;
54 pub const gid_t = void;55 pub const gid_t = void;
55 pub const mode_t = u0;56 pub const mode_t = u0;
57 pub const nlink_t = u0;
56 pub const ino_t = void;58 pub const ino_t = void;
57 pub const IFNAMESIZE = {};59 pub const IFNAMESIZE = {};
58 pub const SIG = void;60 pub const SIG = void;
...@@ -309,274 +311,6 @@ pub fn close(fd: fd_t) void {...@@ -309,274 +311,6 @@ pub fn close(fd: fd_t) void {
309 }311 }
310}312}
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
580pub const RebootError = error{314pub const RebootError = error{
581 PermissionDenied,315 PermissionDenied,
582} || UnexpectedError;316} || UnexpectedError;
...@@ -698,66 +432,6 @@ fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void {...@@ -698,66 +432,6 @@ fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void {
698 }432 }
699}433}
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
761pub const RaiseError = UnexpectedError;435pub const RaiseError = UnexpectedError;
762436
763pub fn raise(sig: SIG) RaiseError!void {437pub fn raise(sig: SIG) RaiseError!void {
...@@ -798,33 +472,6 @@ pub fn kill(pid: pid_t, sig: SIG) KillError!void {...@@ -798,33 +472,6 @@ pub fn kill(pid: pid_t, sig: SIG) KillError!void {
798 }472 }
799}473}
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
828pub const ReadError = std.Io.File.Reader.Error;475pub const ReadError = std.Io.File.Reader.Error;
829476
830/// Returns the number of bytes that were read, which can be less than477/// 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;...@@ -839,34 +486,8 @@ pub const ReadError = std.Io.File.Reader.Error;
839/// The corresponding POSIX limit is `maxInt(isize)`.486/// The corresponding POSIX limit is `maxInt(isize)`.
840pub fn read(fd: fd_t, buf: []u8) ReadError!usize {487pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
841 if (buf.len == 0) return 0;488 if (buf.len == 0) return 0;
842 if (native_os == .windows) {489 if (native_os == .windows) @compileError("unsupported OS");
843 return windows.ReadFile(fd, buf, null);490 if (native_os == .wasi) @compileError("unsupported OS");
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 }
870491
871 // Prevents EINVAL.492 // Prevents EINVAL.
872 const max_count = switch (native_os) {493 const max_count = switch (native_os) {
...@@ -881,7 +502,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -881,7 +502,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
881 .INTR => continue,502 .INTR => continue,
882 .INVAL => unreachable,503 .INVAL => unreachable,
883 .FAULT => unreachable,504 .FAULT => unreachable,
884 .SRCH => return error.ProcessNotFound,
885 .AGAIN => return error.WouldBlock,505 .AGAIN => return error.WouldBlock,
886 .CANCELED => return error.Canceled,506 .CANCELED => return error.Canceled,
887 .BADF => return error.NotOpenForReading, // Can be a race condition.507 .BADF => return error.NotOpenForReading, // Can be a race condition.
...@@ -897,1716 +517,472 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -897,1716 +517,472 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
897 }517 }
898}518}
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).
901///570///
902/// For POSIX systems, if `fd` is opened in non blocking mode, the function will571/// For POSIX systems, if `fd` is opened in non blocking mode, the function will
903/// return error.WouldBlock when EAGAIN is received.572/// return error.WouldBlock when EAGAIN is received.
904/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are573/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
905/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.574/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
906///575///
907/// This operation is non-atomic on the following systems:576/// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000`
908/// * Windows577/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
909/// On these systems, the read races with concurrent writes to the same file descriptor.578/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
910///579/// The limit on Darwin is `0x7fffffff`, trying to read more than that returns EINVAL.
911/// This function assumes that all vectors, including zero-length vectors, have580/// The corresponding POSIX limit is `maxInt(isize)`.
912/// a pointer within the address space of the application.581pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
913pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {582 if (bytes.len == 0) return 0;
914 if (native_os == .windows) {583 if (native_os == .windows) @compileError("unsupported OS");
915 if (iov.len == 0) return 0;584 if (native_os == .wasi) @compileError("unsupported OS");
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 }
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 };
940 while (true) {591 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));
942 switch (errno(rc)) {593 switch (errno(rc)) {
943 .SUCCESS => return @intCast(rc),594 .SUCCESS => return @intCast(rc),
944 .INTR => continue,595 .INTR => continue,
945 .INVAL => unreachable,596 .INVAL => return error.InvalidArgument,
946 .FAULT => unreachable,597 .FAULT => unreachable,
947 .SRCH => return error.ProcessNotFound,
948 .AGAIN => return error.WouldBlock,598 .AGAIN => return error.WouldBlock,
949 .BADF => return error.NotOpenForReading, // can be a race condition599 .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,
950 .IO => return error.InputOutput,603 .IO => return error.InputOutput,
951 .ISDIR => return error.IsDir,604 .NOSPC => return error.NoSpaceLeft,
952 .NOBUFS => return error.SystemResources,605 .ACCES => return error.AccessDenied,
953 .NOMEM => return error.SystemResources,606 .PERM => return error.PermissionDenied,
954 .NOTCONN => return error.SocketUnconnected,607 .PIPE => return error.BrokenPipe,
955 .CONNRESET => return error.ConnectionResetByPeer,608 .CONNRESET => return error.ConnectionResetByPeer,
956 .TIMEDOUT => return error.Timeout,609 .BUSY => return error.DeviceBusy,
610 .NXIO => return error.NoDevice,
611 .MSGSIZE => return error.MessageOversize,
957 else => |err| return unexpectedErrno(err),612 else => |err| return unexpectedErrno(err),
958 }613 }
959 }614 }
960}615}
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.619/// Open and possibly create a file. Keeps trying if it gets interrupted.
965///620/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
966/// Retries when interrupted by a signal.621/// On WASI, `file_path` should be encoded as valid UTF-8.
967///622/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
968/// For POSIX systems, if `fd` is opened in non blocking mode, the function will623/// See also `openZ`.
969/// return error.WouldBlock when EAGAIN is received.624pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
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;
980 if (native_os == .windows) {625 if (native_os == .windows) {
981 return windows.ReadFile(fd, buf, offset);626 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
982 }627 } else if (native_os == .wasi and !builtin.link_libc) {
983 if (native_os == .wasi and !builtin.link_libc) {628 return openat(AT.FDCWD, file_path, flags, perm);
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 }
1010 }629 }
630 const file_path_c = try toPosixPath(file_path);
631 return openZ(&file_path_c, flags, perm);
632}
1011633
1012 // Prevent EINVAL.634/// Open and possibly create a file. Keeps trying if it gets interrupted.
1013 const max_count = switch (native_os) {635/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1014 .linux => 0x7ffff000,636/// On WASI, `file_path` should be encoded as valid UTF-8.
1015 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => maxInt(i32),637/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1016 else => maxInt(isize),638/// See also `open`.
1017 };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;
1020 while (true) {647 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);
1022 switch (errno(rc)) {649 switch (errno(rc)) {
1023 .SUCCESS => return @intCast(rc),650 .SUCCESS => return @intCast(rc),
1024 .INTR => continue,651 .INTR => continue,
1025 .INVAL => unreachable,652
1026 .FAULT => unreachable,653 .FAULT => unreachable,
1027 .SRCH => return error.ProcessNotFound,654 .INVAL => return error.BadPathName,
1028 .AGAIN => return error.WouldBlock,655 .ACCES => return error.AccessDenied,
1029 .BADF => return error.NotOpenForReading, // Can be a race condition.656 .FBIG => return error.FileTooBig,
1030 .IO => return error.InputOutput,657 .OVERFLOW => return error.FileTooBig,
1031 .ISDIR => return error.IsDir,658 .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,
1033 .NOMEM => return error.SystemResources,667 .NOMEM => return error.SystemResources,
1034 .NOTCONN => return error.SocketUnconnected,668 .NOSPC => return error.NoSpaceLeft,
1035 .CONNRESET => return error.ConnectionResetByPeer,669 .NOTDIR => return error.NotDir,
1036 .TIMEDOUT => return error.Timeout,670 .PERM => return error.PermissionDenied,
1037 .NXIO => return error.Unseekable,671 .EXIST => return error.PathAlreadyExists,
1038 .SPIPE => return error.Unseekable,672 .BUSY => return error.DeviceBusy,
1039 .OVERFLOW => return error.Unseekable,673 .ILSEQ => return error.BadPathName,
1040 else => |err| return unexpectedErrno(err),674 else => |err| return unexpectedErrno(err),
1041 }675 }
1042 }676 }
1043}677}
1044678
1045pub const TruncateError = error{679/// Open and possibly create a file. Keeps trying if it gets interrupted.
1046 FileTooBig,680/// `file_path` is relative to the open directory handle `dir_fd`.
1047 InputOutput,681/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1048 FileBusy,682/// On WASI, `file_path` should be encoded as valid UTF-8.
1049 AccessDenied,683/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1050 PermissionDenied,684/// See also `openatZ`.
1051 NonResizable,685pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenError!fd_t {
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
1059 if (native_os == .windows) {686 if (native_os == .windows) {
1060 var io_status_block: windows.IO_STATUS_BLOCK = undefined;687 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
1061 const eof_info: windows.FILE.END_OF_FILE_INFORMATION = .{688 } else if (native_os == .wasi and !builtin.link_libc) {
1062 .EndOfFile = signed_len,689 @compileError("use std.Io instead");
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 }
1079 }690 }
1080 if (native_os == .wasi and !builtin.link_libc) {691 const file_path_c = try toPosixPath(file_path);
1081 switch (wasi.fd_filestat_set_size(fd, length)) {692 return openatZ(dir_fd, &file_path_c, flags, mode);
1082 .SUCCESS => return,693}
1083 .INTR => unreachable,694
1084 .FBIG => return error.FileTooBig,695/// Open and possibly create a file. Keeps trying if it gets interrupted.
1085 .IO => return error.InputOutput,696/// `file_path` is relative to the open directory handle `dir_fd`.
1086 .PERM => return error.PermissionDenied,697/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1087 .TXTBSY => return error.FileBusy,698/// On WASI, `file_path` should be encoded as valid UTF-8.
1088 .BADF => unreachable, // Handle not open for writing699/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1089 .INVAL => return error.NonResizable,700/// See also `openat`.
1090 .NOTCAPABLE => return error.AccessDenied,701pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t {
1091 else => |err| return unexpectedErrno(err),702 if (native_os == .windows) {
1092 }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);
1093 }706 }
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;
1096 while (true) {709 while (true) {
1097 switch (errno(ftruncate_sym(fd, signed_len))) {710 const rc = openat_sym(dir_fd, file_path, flags, mode);
1098 .SUCCESS => return,711 switch (errno(rc)) {
712 .SUCCESS => return @intCast(rc),
1099 .INTR => continue,713 .INTR => continue,
714
715 .FAULT => unreachable,
716 .INVAL => return error.BadPathName,
717 .BADF => unreachable,
718 .ACCES => return error.AccessDenied,
1100 .FBIG => return error.FileTooBig,719 .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,
1102 .PERM => return error.PermissionDenied,732 .PERM => return error.PermissionDenied,
733 .EXIST => return error.PathAlreadyExists,
734 .BUSY => return error.DeviceBusy,
735 .OPNOTSUPP => return error.FileLocksUnsupported,
736 .AGAIN => return error.WouldBlock,
1103 .TXTBSY => return error.FileBusy,737 .TXTBSY => return error.FileBusy,
1104 .BADF => unreachable, // Handle not open for writing738 .NXIO => return error.NoDevice,
1105 .INVAL => return error.NonResizable, // This is returned for /dev/null for example.739 .ILSEQ => return error.BadPathName,
1106 else => |err| return unexpectedErrno(err),740 else => |err| return unexpectedErrno(err),
1107 }741 }
1108 }742 }
1109}743}
1110744
1111/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.745pub fn dup(old_fd: fd_t) !fd_t {
1112///746 const rc = system.dup(old_fd);
1113/// Retries when interrupted by a signal.747 return switch (errno(rc)) {
1114///748 .SUCCESS => return @intCast(rc),
1115/// For POSIX systems, if `fd` is opened in non blocking mode, the function will749 .MFILE => error.ProcessFdQuotaExceeded,
1116/// return error.WouldBlock when EAGAIN is received.750 .BADF => unreachable, // invalid file descriptor
1117/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are751 else => |err| return unexpectedErrno(err),
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,
1128 };752 };
1129 if (have_pread_but_not_preadv) {753}
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 }
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 {
1161 while (true) {756 while (true) {
1162 const rc = preadv_sym(fd, iov.ptr, @min(iov.len, IOV_MAX), @bitCast(offset));757 switch (errno(system.dup2(old_fd, new_fd))) {
1163 switch (errno(rc)) {758 .SUCCESS => return,
1164 .SUCCESS => return @bitCast(rc),759 .BUSY, .INTR => continue,
1165 .INTR => continue,760 .MFILE => return error.ProcessFdQuotaExceeded,
1166 .INVAL => unreachable,761 .INVAL => unreachable, // invalid parameters passed to dup2
1167 .FAULT => unreachable,762 .BADF => unreachable, // invalid file descriptor
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,
1181 else => |err| return unexpectedErrno(err),763 else => |err| return unexpectedErrno(err),
1182 }764 }
1183 }765 }
1184}766}
1185767
1186pub const WriteError = error{768pub fn getpid() pid_t {
1187 DiskQuota,769 return system.getpid();
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 };
2300}770}
2301771
2302pub const UnlinkatError = UnlinkError || error{772pub fn getppid() pid_t {
2303 /// When passing `AT.REMOVEDIR`, this error occurs when the named directory is not empty.773 return system.getppid();
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 }
2322}774}
2323775
2324/// WASI-only. Same as `unlinkat` but targeting WASI.776pub const ExecveError = error{
2325/// See also `unlinkat`.777 SystemResources,
2326pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {778 AccessDenied,
2327 const remove_dir = (flags & AT.REMOVEDIR) != 0;779 PermissionDenied,
2328 const res = if (remove_dir)780 InvalidExe,
2329 wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len)781 FileSystem,
2330 else782 IsDir,
2331 wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len);783 FileNotFound,
2332 switch (res) {784 NotDir,
2333 .SUCCESS => return,785 FileBusy,
2334 .ACCES => return error.AccessDenied,786 ProcessFdQuotaExceeded,
2335 .PERM => return error.PermissionDenied,787 SystemFdQuotaExceeded,
2336 .BUSY => return error.FileBusy,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,
2337 .FAULT => unreachable,799 .FAULT => unreachable,
2338 .IO => return error.FileSystem,800 .@"2BIG" => return error.SystemResources,
2339 .ISDIR => return error.IsDir,801 .MFILE => return error.ProcessFdQuotaExceeded,
2340 .LOOP => return error.SymLinkLoop,
2341 .NAMETOOLONG => return error.NameTooLong,802 .NAMETOOLONG => return error.NameTooLong,
2342 .NOENT => return error.FileNotFound,803 .NFILE => return error.SystemFdQuotaExceeded,
2343 .NOTDIR => return error.NotDir,
2344 .NOMEM => return error.SystemResources,804 .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,
2367 .ACCES => return error.AccessDenied,805 .ACCES => return error.AccessDenied,
2368 .PERM => return error.PermissionDenied,806 .PERM => return error.PermissionDenied,
2369 .BUSY => return error.FileBusy,807 .INVAL => return error.InvalidExe,
2370 .FAULT => unreachable,808 .NOEXEC => return error.InvalidExe,
2371 .IO => return error.FileSystem,809 .IO => return error.FileSystem,
810 .LOOP => return error.FileSystem,
2372 .ISDIR => return error.IsDir,811 .ISDIR => return error.IsDir,
2373 .LOOP => return error.SymLinkLoop,
2374 .NAMETOOLONG => return error.NameTooLong,
2375 .NOENT => return error.FileNotFound,812 .NOENT => return error.FileNotFound,
2376 .NOTDIR => return error.NotDir,813 .NOTDIR => return error.NotDir,
2377 .NOMEM => return error.SystemResources,814 .TXTBSY => return error.FileBusy,
2378 .ROFS => return error.ReadOnlyFileSystem,815 else => |err| switch (native_os) {
2379 .EXIST => return error.DirNotEmpty,816 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) {
2380 .NOTEMPTY => return error.DirNotEmpty,817 .BADEXEC => return error.InvalidExe,
2381 .ILSEQ => return error.BadPathName,818 .BADARCH => return error.InvalidExe,
2382819 else => return unexpectedErrno(err),
2383 .INVAL => unreachable, // invalid flags, or pathname has . as last component820 },
2384 .BADF => unreachable, // always a race condition821 .linux => switch (err) {
2385822 .LIBBAD => return error.InvalidExe,
2386 else => |err| return unexpectedErrno(err),823 else => return unexpectedErrno(err),
824 },
825 else => return unexpectedErrno(err),
826 },
2387 }827 }
2388}828}
2389829
2390/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only.830pub const Arg0Expand = enum {
2391pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {831 expand,
2392 const remove_dir = (flags & AT.REMOVEDIR) != 0;832 no_expand,
2393 return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });833};
2394}
2395834
2396pub const RenameError = error{835/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable,
2397 /// In WASI, this error may occur when the file descriptor does836/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall.
2398 /// not hold the required rights to rename a resource by path relative to it.837/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in.
2399 ///838pub fn execvpeZ_expandArg0(
2400 /// On Windows, this error may be returned instead of PathAlreadyExists when839 comptime arg0_expand: Arg0Expand,
2401 /// renaming a directory over an existing directory.840 file: [*:0]const u8,
2402 AccessDenied,841 child_argv: switch (arg0_expand) {
2403 PermissionDenied,842 .expand => [*:null]?[*:0]const u8,
2404 FileBusy,843 .no_expand => [*:null]const ?[*:0]const u8,
2405 DiskQuota,844 },
2406 IsDir,845 envp: [*:null]const ?[*:0]const u8,
2407 SymLinkLoop,846) ExecveError {
2408 LinkQuotaExceeded,847 const file_slice = mem.sliceTo(file, 0);
2409 NameTooLong,848 if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp);
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;
2433849
2434/// Change the name or location of a file.850 const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
2435/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).851 // Use of PATH_MAX here is valid as the path_buf will be passed
2436/// On WASI, both paths should be encoded as valid UTF-8.852 // directly to the operating system in execveZ.
2437/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.853 var path_buf: [PATH_MAX]u8 = undefined;
2438pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {854 var it = mem.tokenizeScalar(u8, PATH, ':');
2439 if (native_os == .wasi and !builtin.link_libc) {855 var seen_eacces = false;
2440 return renameat(AT.FDCWD, old_path, AT.FDCWD, new_path);856 var err: ExecveError = error.FileNotFound;
2441 } else if (native_os == .windows) {857
2442 const old_path_w = try windows.sliceToPrefixedFileW(null, old_path);858 // In case of expanding arg0 we must put it back if we return with an error.
2443 const new_path_w = try windows.sliceToPrefixedFileW(null, new_path);859 const prev_arg0 = child_argv[0];
2444 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);860 defer switch (arg0_expand) {
2445 } else {861 .expand => child_argv[0] = prev_arg0,
2446 const old_path_c = try toPosixPath(old_path);862 .no_expand => {},
2447 const new_path_c = try toPosixPath(new_path);863 };
2448 return renameZ(&old_path_c, &new_path_c);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 }
2449 }883 }
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);
2450}896}
2451897
2452/// Same as `rename` except the parameters are null-terminated.898/// Get an environment variable.
2453pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {899/// See also `getenvZ`.
900pub fn getenv(key: []const u8) ?[:0]const u8 {
2454 if (native_os == .windows) {901 if (native_os == .windows) {
2455 const old_path_w = try windows.cStrToPrefixedFileW(null, old_path);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.");
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));
2460 }903 }
2461 switch (errno(system.rename(old_path, new_path))) {904 if (mem.findScalar(u8, key, '=') != null) {
2462 .SUCCESS => return,905 return null;
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),
2483 }906 }
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.917 return mem.sliceTo(line + line_i + 1, 0);
2487/// Assumes target is Windows.918 }
2488pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {919 return null;
2489 const cwd_handle = std.fs.cwd().fd;920 }
2490 return windows.RenameFile(cwd_handle, mem.span(old_path), cwd_handle, mem.span(new_path), true);921 if (native_os == .wasi) {
2491}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.935 return mem.sliceTo(ptr + line_i + 1, 0);
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);
2515 }936 }
937 return null;
2516}938}
2517939
2518/// WASI-only. Same as `renameat` expect targeting WASI.940/// Get an environment variable with a null-terminated name.
2519/// See also `renameat`.941/// See also `getenv`.
2520fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {942pub fn getenvZ(key: [*:0]const u8) ?[:0]const u8 {
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)) {943 if (builtin.link_libc) {
2522 .SUCCESS => return,944 const value = system.getenv(key) orelse return null;
2523 .ACCES => return error.AccessDenied,945 return mem.sliceTo(value, 0);
2524 .PERM => return error.PermissionDenied,946 }
2525 .BUSY => return error.FileBusy,947 if (native_os == .windows) {
2526 .DQUOT => return error.DiskQuota,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.");
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),
2544 }949 }
950 return getenv(mem.sliceTo(key, 0));
2545}951}
2546952
2547/// An fd-relative file path953pub const GetCwdError = error{
2548///954 NameTooLong,
2549/// This is currently only used for WASI-specific functionality, but the concept955 CurrentWorkingDirectoryUnlinked,
2550/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions.956} || UnexpectedError;
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};
2557957
2558/// Same as `renameat` except the parameters are null-terminated.958/// The result is a slice of out_buffer, indexed from 0.
2559pub fn renameatZ(959pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
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 {
2565 if (native_os == .windows) {960 if (native_os == .windows) {
2566 const old_path_w = try windows.cStrToPrefixedFileW(old_dir_fd, old_path);961 return windows.GetCurrentDirectory(out_buffer);
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);
2569 } else if (native_os == .wasi and !builtin.link_libc) {962 } 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;
2571 }968 }
2572969
2573 switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {970 const err: E = if (builtin.link_libc) err: {
2574 .SUCCESS => return,971 const c_err = if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*;
2575 .ACCES => return error.AccessDenied,972 break :err @enumFromInt(c_err);
2576 .PERM => return error.PermissionDenied,973 } else err: {
2577 .BUSY => return error.FileBusy,974 break :err errno(system.getcwd(out_buffer.ptr, out_buffer.len));
2578 .DQUOT => return error.DiskQuota,975 };
976 switch (err) {
977 .SUCCESS => return mem.sliceTo(out_buffer, 0),
2579 .FAULT => unreachable,978 .FAULT => unreachable,
2580 .INVAL => unreachable,979 .INVAL => unreachable,
2581 .ISDIR => return error.IsDir,980 .NOENT => return error.CurrentWorkingDirectoryUnlinked,
2582 .LOOP => return error.SymLinkLoop,981 .RANGE => return error.NameTooLong,
2583 .MLINK => return error.LinkQuotaExceeded,982 else => return unexpectedErrno(err),
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),
2595 }983 }
2596}984}
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
2610/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).986/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2611/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.987/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
2612/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.988/// 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...@@ -2651,7 +1027,7 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDir
2651 }1027 }
2652}1028}
26531029
2654pub const MakeDirError = std.Io.Dir.MakeError;1030pub const MakeDirError = std.Io.Dir.CreateDirError;
26551031
2656/// Create a directory.1032/// Create a directory.
2657/// `mode` is ignored on Windows and WASI.1033/// `mode` is ignored on Windows and WASI.
...@@ -2705,7 +1081,7 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {...@@ -2705,7 +1081,7 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
2705pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {1081pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
2706 _ = mode;1082 _ = mode;
2707 const sub_dir_handle = windows.OpenFile(dir_path_w, .{1083 const sub_dir_handle = windows.OpenFile(dir_path_w, .{
2708 .dir = fs.cwd().fd,1084 .dir = Io.Dir.cwd().handle,
2709 .access_mask = .{1085 .access_mask = .{
2710 .STANDARD = .{ .SYNCHRONIZE = true },1086 .STANDARD = .{ .SYNCHRONIZE = true },
2711 .GENERIC = .{ .READ = true },1087 .GENERIC = .{ .READ = true },
...@@ -2723,84 +1099,6 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {...@@ -2723,84 +1099,6 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
2723 windows.CloseHandle(sub_dir_handle);1099 windows.CloseHandle(sub_dir_handle);
2724}1100}
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
2804pub const ChangeCurDirError = error{1102pub const ChangeCurDirError = error{
2805 AccessDenied,1103 AccessDenied,
2806 FileSystem,1104 FileSystem,
...@@ -2821,11 +1119,9 @@ pub const ChangeCurDirError = error{...@@ -2821,11 +1119,9 @@ pub const ChangeCurDirError = error{
2821/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.1119/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
2822pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {1120pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
2823 if (native_os == .wasi and !builtin.link_libc) {1121 if (native_os == .wasi and !builtin.link_libc) {
2824 @compileError("WASI does not support os.chdir");1122 @compileError("unsupported OS");
2825 } else if (native_os == .windows) {1123 } else if (native_os == .windows) {
2826 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;1124 @compileError("unsupported OS");
2827 const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path);
2828 return chdirW(wtf16_dir_path[0..len]);
2829 } else {1125 } else {
2830 const dir_path_c = try toPosixPath(dir_path);1126 const dir_path_c = try toPosixPath(dir_path);
2831 return chdirZ(&dir_path_c);1127 return chdirZ(&dir_path_c);
...@@ -2838,12 +1134,9 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {...@@ -2838,12 +1134,9 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
2838/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.1134/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
2839pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {1135pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
2840 if (native_os == .windows) {1136 if (native_os == .windows) {
2841 const dir_path_span = mem.span(dir_path);1137 @compileError("unsupported OS");
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]);
2845 } else if (native_os == .wasi and !builtin.link_libc) {1138 } else if (native_os == .wasi and !builtin.link_libc) {
2846 return chdir(mem.span(dir_path));1139 @compileError("unsupported OS");
2847 }1140 }
2848 switch (errno(system.chdir(dir_path))) {1141 switch (errno(system.chdir(dir_path))) {
2849 .SUCCESS => return,1142 .SUCCESS => return,
...@@ -2860,14 +1153,6 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {...@@ -2860,14 +1153,6 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
2860 }1153 }
2861}1154}
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
2871pub const FchdirError = error{1156pub const FchdirError = error{
2872 AccessDenied,1157 AccessDenied,
2873 NotDir,1158 NotDir,
...@@ -2889,194 +1174,6 @@ pub fn fchdir(dirfd: fd_t) FchdirError!void {...@@ -2889,194 +1174,6 @@ pub fn fchdir(dirfd: fd_t) FchdirError!void {
2889 }1174 }
2890}1175}
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
3080pub const SetEidError = error{1177pub const SetEidError = error{
3081 InvalidUserId,1178 InvalidUserId,
3082 PermissionDenied,1179 PermissionDenied,
...@@ -3176,47 +1273,6 @@ pub fn getegid() gid_t {...@@ -3176,47 +1273,6 @@ pub fn getegid() gid_t {
3176 return system.getegid();1273 return system.getegid();
3177}1274}
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
3220pub const SocketError = error{1276pub const SocketError = error{
3221 /// Permission to create a socket of the specified type and/or1277 /// Permission to create a socket of the specified type and/or
3222 /// pro‐tocol is denied.1278 /// pro‐tocol is denied.
...@@ -3406,7 +1462,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi...@@ -3406,7 +1462,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi
34061462
3407pub const ListenError = error{1463pub const ListenError = error{
3408 FileDescriptorNotASocket,1464 FileDescriptorNotASocket,
3409 OperationNotSupported,1465 OperationUnsupported,
3410} || std.Io.net.IpAddress.ListenError || std.Io.net.UnixAddress.ListenError;1466} || std.Io.net.IpAddress.ListenError || std.Io.net.UnixAddress.ListenError;
34111467
3412pub fn listen(sock: socket_t, backlog: u31) ListenError!void {1468pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
...@@ -3419,7 +1475,7 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void {...@@ -3419,7 +1475,7 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
3419 .ADDRINUSE => return error.AddressInUse,1475 .ADDRINUSE => return error.AddressInUse,
3420 .BADF => unreachable,1476 .BADF => unreachable,
3421 .NOTSOCK => return error.FileDescriptorNotASocket,1477 .NOTSOCK => return error.FileDescriptorNotASocket,
3422 .OPNOTSUPP => return error.OperationNotSupported,1478 .OPNOTSUPP => return error.OperationUnsupported,
3423 else => |err| return unexpectedErrno(err),1479 else => |err| return unexpectedErrno(err),
3424 }1480 }
3425 }1481 }
...@@ -4081,7 +2137,7 @@ pub const FanotifyMarkError = error{...@@ -4081,7 +2137,7 @@ pub const FanotifyMarkError = error{
4081 SystemResources,2137 SystemResources,
4082 UserMarkQuotaExceeded,2138 UserMarkQuotaExceeded,
4083 NotDir,2139 NotDir,
4084 OperationNotSupported,2140 OperationUnsupported,
4085 PermissionDenied,2141 PermissionDenied,
4086 NotSameFileSystem,2142 NotSameFileSystem,
4087 NameTooLong,2143 NameTooLong,
...@@ -4121,7 +2177,7 @@ pub fn fanotify_markZ(...@@ -4121,7 +2177,7 @@ pub fn fanotify_markZ(
4121 .NOMEM => return error.SystemResources,2177 .NOMEM => return error.SystemResources,
4122 .NOSPC => return error.UserMarkQuotaExceeded,2178 .NOSPC => return error.UserMarkQuotaExceeded,
4123 .NOTDIR => return error.NotDir,2179 .NOTDIR => return error.NotDir,
4124 .OPNOTSUPP => return error.OperationNotSupported,2180 .OPNOTSUPP => return error.OperationUnsupported,
4125 .PERM => return error.PermissionDenied,2181 .PERM => return error.PermissionDenied,
4126 .XDEV => return error.NotSameFileSystem,2182 .XDEV => return error.NotSameFileSystem,
4127 else => |err| return unexpectedErrno(err),2183 else => |err| return unexpectedErrno(err),
...@@ -4370,75 +2426,14 @@ pub const MSyncError = error{...@@ -4370,75 +2426,14 @@ pub const MSyncError = error{
4370 UnmappedMemory,2426 UnmappedMemory,
4371 PermissionDenied,2427 PermissionDenied,
4372} || UnexpectedError;2428} || UnexpectedError;
43732429
4374pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void {2430pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void {
4375 switch (errno(system.msync(memory.ptr, memory.len, flags))) {2431 switch (errno(system.msync(memory.ptr, memory.len, flags))) {
4376 .SUCCESS => return,2432 .SUCCESS => return,
4377 .PERM => return error.PermissionDenied,2433 .PERM => return error.PermissionDenied,
4378 .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory2434 .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory
4379 .INVAL => unreachable, // Invalid parameters.2435 .INVAL => unreachable, // Invalid parameters.
4380 else => unreachable,2436 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),
4442 }2437 }
4443}2438}
44442439
...@@ -4586,177 +2581,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {...@@ -4586,177 +2581,6 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
4586 }2581 }
4587}2582}
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
4760pub const FcntlError = error{2584pub const FcntlError = error{
4761 PermissionDenied,2585 PermissionDenied,
4762 FileBusy,2586 FileBusy,
...@@ -4786,185 +2610,6 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {...@@ -4786,185 +2610,6 @@ pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize {
4786 }2610 }
4787}2611}
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
4968/// Spurious wakeups are possible and no precision of timing is guaranteed.2613/// Spurious wakeups are possible and no precision of timing is guaranteed.
4969pub fn nanosleep(seconds: u64, nanoseconds: u64) void {2614pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
4970 var req = timespec{2615 var req = timespec{
...@@ -5049,16 +2694,16 @@ pub fn dl_iterate_phdr(...@@ -5049,16 +2694,16 @@ pub fn dl_iterate_phdr(
50492694
5050 // Last return value from the callback function.2695 // Last return value from the callback function.
5051 while (it.next()) |entry| {2696 while (it.next()) |entry| {
5052 const phdrs: []elf.ElfN.Phdr = if (entry.l_addr != 0) phdrs: {2697 const phdrs: []elf.ElfN.Phdr = if (entry.addr != 0) phdrs: {
5053 const ehdr: *elf.ElfN.Ehdr = @ptrFromInt(entry.l_addr);2698 const ehdr: *elf.ElfN.Ehdr = @ptrFromInt(entry.addr);
5054 assert(mem.eql(u8, ehdr.ident[0..4], elf.MAGIC));2699 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);
5056 break :phdrs phdrs[0..ehdr.phnum];2701 break :phdrs phdrs[0..ehdr.phnum];
5057 } else getSelfPhdrs();2702 } else getSelfPhdrs();
50582703
5059 var info: dl_phdr_info = .{2704 var info: dl_phdr_info = .{
5060 .addr = entry.l_addr,2705 .addr = entry.addr,
5061 .name = entry.l_name,2706 .name = entry.name,
5062 .phdr = phdrs.ptr,2707 .phdr = phdrs.ptr,
5063 .phnum = @intCast(phdrs.len),2708 .phnum = @intCast(phdrs.len),
5064 };2709 };
...@@ -5229,74 +2874,6 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*...@@ -5229,74 +2874,6 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*
5229 }2874 }
5230}2875}
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
5300pub const GetHostNameError = error{PermissionDenied} || UnexpectedError;2877pub const GetHostNameError = error{PermissionDenied} || UnexpectedError;
53012878
5302pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {2879pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 {
...@@ -5612,98 +3189,6 @@ pub fn send(...@@ -5612,98 +3189,6 @@ pub fn send(
5612 };3189 };
5613}3190}
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
5707pub const PollError = error{3192pub const PollError = error{
5708 /// The network subsystem has failed.3193 /// The network subsystem has failed.
5709 NetworkDown,3194 NetworkDown,
...@@ -5916,7 +3401,7 @@ pub const SetSockOptError = error{...@@ -5916,7 +3401,7 @@ pub const SetSockOptError = error{
5916 /// Setting the socket option requires more elevated permissions.3401 /// Setting the socket option requires more elevated permissions.
5917 PermissionDenied,3402 PermissionDenied,
59183403
5919 OperationNotSupported,3404 OperationUnsupported,
5920 NetworkDown,3405 NetworkDown,
5921 FileDescriptorNotASocket,3406 FileDescriptorNotASocket,
5922 SocketNotBound,3407 SocketNotBound,
...@@ -5952,7 +3437,7 @@ pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSo...@@ -5952,7 +3437,7 @@ pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSo
5952 .NOBUFS => return error.SystemResources,3437 .NOBUFS => return error.SystemResources,
5953 .PERM => return error.PermissionDenied,3438 .PERM => return error.PermissionDenied,
5954 .NODEV => return error.NoDevice,3439 .NODEV => return error.NoDevice,
5955 .OPNOTSUPP => return error.OperationNotSupported,3440 .OPNOTSUPP => return error.OperationUnsupported,
5956 else => |err| return unexpectedErrno(err),3441 else => |err| return unexpectedErrno(err),
5957 }3442 }
5958 }3443 }
...@@ -6118,12 +3603,7 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t {...@@ -6118,12 +3603,7 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t {
6118 }3603 }
6119}3604}
61203605
6121pub const SyncError = error{3606pub const SyncError = std.Io.File.SyncError;
6122 InputOutput,
6123 NoSpaceLeft,
6124 DiskQuota,
6125 AccessDenied,
6126} || UnexpectedError;
61273607
6128/// Write all pending file contents and metadata modifications to all filesystems.3608/// Write all pending file contents and metadata modifications to all filesystems.
6129pub fn sync() void {3609pub fn sync() void {
...@@ -6143,38 +3623,8 @@ pub fn syncfs(fd: fd_t) SyncError!void {...@@ -6143,38 +3623,8 @@ pub fn syncfs(fd: fd_t) SyncError!void {
6143 }3623 }
6144}3624}
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
6170/// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata.3626/// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata.
6171pub fn fdatasync(fd: fd_t) SyncError!void {3627pub 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 }
6178 const rc = system.fdatasync(fd);3628 const rc = system.fdatasync(fd);
6179 switch (errno(rc)) {3629 switch (errno(rc)) {
6180 .SUCCESS => return,3630 .SUCCESS => return,
...@@ -6197,7 +3647,7 @@ pub const PrctlError = error{...@@ -6197,7 +3647,7 @@ pub const PrctlError = error{
6197 /// or PR_MPX_DISABLE_MANAGEMENT3647 /// or PR_MPX_DISABLE_MANAGEMENT
6198 UnsupportedFeature,3648 UnsupportedFeature,
6199 /// Can only occur with PR_SET_FP_MODE3649 /// Can only occur with PR_SET_FP_MODE
6200 OperationNotSupported,3650 OperationUnsupported,
6201 PermissionDenied,3651 PermissionDenied,
6202} || UnexpectedError;3652} || UnexpectedError;
62033653
...@@ -6221,7 +3671,7 @@ pub fn prctl(option: PR, args: anytype) PrctlError!u31 {...@@ -6221,7 +3671,7 @@ pub fn prctl(option: PR, args: anytype) PrctlError!u31 {
6221 .FAULT => return error.InvalidAddress,3671 .FAULT => return error.InvalidAddress,
6222 .INVAL => unreachable,3672 .INVAL => unreachable,
6223 .NODEV, .NXIO => return error.UnsupportedFeature,3673 .NODEV, .NXIO => return error.UnsupportedFeature,
6224 .OPNOTSUPP => return error.OperationNotSupported,3674 .OPNOTSUPP => return error.OperationUnsupported,
6225 .PERM, .BUSY => return error.PermissionDenied,3675 .PERM, .BUSY => return error.PermissionDenied,
6226 .RANGE => unreachable,3676 .RANGE => unreachable,
6227 else => |err| return unexpectedErrno(err),3677 else => |err| return unexpectedErrno(err),
...@@ -6480,7 +3930,7 @@ pub const PtraceError = error{...@@ -6480,7 +3930,7 @@ pub const PtraceError = error{
6480 DeviceBusy,3930 DeviceBusy,
6481 InputOutput,3931 InputOutput,
6482 NameTooLong,3932 NameTooLong,
6483 OperationNotSupported,3933 OperationUnsupported,
6484 OutOfMemory,3934 OutOfMemory,
6485 ProcessNotFound,3935 ProcessNotFound,
6486 PermissionDenied,3936 PermissionDenied,
...@@ -6582,7 +4032,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo...@@ -6582,7 +4032,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo
6582 .INVAL => unreachable,4032 .INVAL => unreachable,
6583 .PERM => error.PermissionDenied,4033 .PERM => error.PermissionDenied,
6584 .BUSY => error.DeviceBusy,4034 .BUSY => error.DeviceBusy,
6585 .NOTSUP => error.OperationNotSupported,4035 .NOTSUP => error.OperationUnsupported,
6586 else => |err| return unexpectedErrno(err),4036 else => |err| return unexpectedErrno(err),
6587 },4037 },
65884038
...@@ -6593,7 +4043,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo...@@ -6593,7 +4043,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo
6593pub const NameToFileHandleAtError = error{4043pub const NameToFileHandleAtError = error{
6594 FileNotFound,4044 FileNotFound,
6595 NotDir,4045 NotDir,
6596 OperationNotSupported,4046 OperationUnsupported,
6597 NameTooLong,4047 NameTooLong,
6598 Unexpected,4048 Unexpected,
6599};4049};
...@@ -6622,7 +4072,7 @@ pub fn name_to_handle_atZ(...@@ -6622,7 +4072,7 @@ pub fn name_to_handle_atZ(
6622 .INVAL => unreachable, // bad flags, or handle_bytes too big4072 .INVAL => unreachable, // bad flags, or handle_bytes too big
6623 .NOENT => return error.FileNotFound,4073 .NOENT => return error.FileNotFound,
6624 .NOTDIR => return error.NotDir,4074 .NOTDIR => return error.NotDir,
6625 .OPNOTSUPP => return error.OperationNotSupported,4075 .OPNOTSUPP => return error.OperationUnsupported,
6626 .OVERFLOW => return error.NameTooLong,4076 .OVERFLOW => return error.NameTooLong,
6627 else => |err| return unexpectedErrno(err),4077 else => |err| return unexpectedErrno(err),
6628 }4078 }
lib/std/posix/test.zig+84-554
...@@ -1,34 +1,24 @@...@@ -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
1const std = @import("../std.zig");6const std = @import("../std.zig");
7const Io = std.Io;
8const Dir = std.Io.Dir;
2const posix = std.posix;9const posix = std.posix;
3const testing = std.testing;
4const expect = testing.expect;
5const expectEqual = testing.expectEqual;
6const expectError = testing.expectError;
7const fs = std.fs;
8const mem = std.mem;10const mem = std.mem;
9const elf = std.elf;11const elf = std.elf;
10const linux = std.os.linux;12const linux = std.os.linux;
13const AT = std.posix.AT;
1114
12const a = std.testing.allocator;15const testing = std.testing;
1316const expect = std.testing.expect;
14const builtin = @import("builtin");17const expectEqual = std.testing.expectEqual;
15const AtomicRmwOp = std.builtin.AtomicRmwOp;18const expectEqualSlices = std.testing.expectEqualSlices;
16const AtomicOrder = std.builtin.AtomicOrder;19const expectEqualStrings = std.testing.expectEqualStrings;
17const native_os = builtin.target.os.tag;20const expectError = std.testing.expectError;
18const tmpDir = std.testing.tmpDir;21const 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
33test "check WASI CWD" {23test "check WASI CWD" {
34 if (native_os == .wasi) {24 if (native_os == .wasi) {
...@@ -43,206 +33,6 @@ test "check WASI CWD" {...@@ -43,206 +33,6 @@ test "check WASI CWD" {
43 }33 }
44}34}
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
246test "getrandom" {36test "getrandom" {
247 var buf_a: [50]u8 = undefined;37 var buf_a: [50]u8 = undefined;
248 var buf_b: [50]u8 = undefined;38 var buf_b: [50]u8 = undefined;
...@@ -273,7 +63,7 @@ test "sigaltstack" {...@@ -273,7 +63,7 @@ test "sigaltstack" {
273 // Setting a stack size less than MINSIGSTKSZ returns ENOMEM63 // Setting a stack size less than MINSIGSTKSZ returns ENOMEM
274 st.flags = 0;64 st.flags = 0;
275 st.size = 1;65 st.size = 1;
276 try testing.expectError(error.SizeTooSmall, posix.sigaltstack(&st, null));66 try expectError(error.SizeTooSmall, posix.sigaltstack(&st, null));
277}67}
27868
279// If the type is not available use void to avoid erroring out when `iter_fn` is69// If the type is not available use void to avoid erroring out when `iter_fn` is
...@@ -345,7 +135,7 @@ test "pipe" {...@@ -345,7 +135,7 @@ test "pipe" {
345 try expect((try posix.write(fds[1], "hello")) == 5);135 try expect((try posix.write(fds[1], "hello")) == 5);
346 var buf: [16]u8 = undefined;136 var buf: [16]u8 = undefined;
347 try expect((try posix.read(fds[0], buf[0..])) == 5);137 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");
349 posix.close(fds[1]);139 posix.close(fds[1]);
350 posix.close(fds[0]);140 posix.close(fds[0]);
351}141}
...@@ -356,6 +146,8 @@ test "argsAlloc" {...@@ -356,6 +146,8 @@ test "argsAlloc" {
356}146}
357147
358test "memfd_create" {148test "memfd_create" {
149 const io = testing.io;
150
359 // memfd_create is only supported by linux and freebsd.151 // memfd_create is only supported by linux and freebsd.
360 switch (native_os) {152 switch (native_os) {
361 .linux => {},153 .linux => {},
...@@ -366,21 +158,22 @@ test "memfd_create" {...@@ -366,21 +158,22 @@ test "memfd_create" {
366 else => return error.SkipZigTest,158 else => return error.SkipZigTest,
367 }159 }
368160
369 const fd = try posix.memfd_create("test", 0);161 const file: Io.File = .{ .handle = try posix.memfd_create("test", 0) };
370 defer posix.close(fd);162 defer file.close(io);
371 try expect((try posix.write(fd, "test")) == 4);163 try file.writePositionalAll(io, "test", 0);
372 try posix.lseek_SET(fd, 0);
373164
374 var buf: [10]u8 = undefined;165 var buf: [10]u8 = undefined;
375 const bytes_read = try posix.read(fd, &buf);166 const bytes_read = try file.readPositionalAll(io, &buf, 0);
376 try expect(bytes_read == 4);167 try expect(bytes_read == 4);
377 try expect(mem.eql(u8, buf[0..4], "test"));168 try expectEqualStrings("test", buf[0..4]);
378}169}
379170
380test "mmap" {171test "mmap" {
381 if (native_os == .windows or native_os == .wasi)172 if (native_os == .windows or native_os == .wasi)
382 return error.SkipZigTest;173 return error.SkipZigTest;
383174
175 const io = testing.io;
176
384 var tmp = tmpDir(.{});177 var tmp = tmpDir(.{});
385 defer tmp.cleanup();178 defer tmp.cleanup();
386179
...@@ -396,14 +189,14 @@ test "mmap" {...@@ -396,14 +189,14 @@ test "mmap" {
396 );189 );
397 defer posix.munmap(data);190 defer posix.munmap(data);
398191
399 try testing.expectEqual(@as(usize, 1234), data.len);192 try expectEqual(@as(usize, 1234), data.len);
400193
401 // By definition the data returned by mmap is zero-filled194 // 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
404 // Make sure the memory is writeable as requested197 // Make sure the memory is writeable as requested
405 @memset(data, 0x55);198 @memset(data, 0x55);
406 try testing.expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234));199 try expect(mem.eql(u8, data, &[_]u8{0x55} ** 1234));
407 }200 }
408201
409 const test_out_file = "os_tmp_test";202 const test_out_file = "os_tmp_test";
...@@ -412,10 +205,10 @@ test "mmap" {...@@ -412,10 +205,10 @@ test "mmap" {
412205
413 // Create a file used for testing mmap() calls with a file descriptor206 // Create a file used for testing mmap() calls with a file descriptor
414 {207 {
415 const file = try tmp.dir.createFile(test_out_file, .{});208 const file = try tmp.dir.createFile(io, test_out_file, .{});
416 defer file.close();209 defer file.close(io);
417210
418 var stream = file.writer(&.{});211 var stream = file.writer(io, &.{});
419212
420 var i: usize = 0;213 var i: usize = 0;
421 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {214 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
...@@ -425,8 +218,8 @@ test "mmap" {...@@ -425,8 +218,8 @@ test "mmap" {
425218
426 // Map the whole file219 // Map the whole file
427 {220 {
428 const file = try tmp.dir.openFile(test_out_file, .{});221 const file = try tmp.dir.openFile(io, test_out_file, .{});
429 defer file.close();222 defer file.close(io);
430223
431 const data = try posix.mmap(224 const data = try posix.mmap(
432 null,225 null,
...@@ -442,7 +235,7 @@ test "mmap" {...@@ -442,7 +235,7 @@ test "mmap" {
442235
443 var i: usize = 0;236 var i: usize = 0;
444 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {237 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));
446 }239 }
447 }240 }
448241
...@@ -450,8 +243,8 @@ test "mmap" {...@@ -450,8 +243,8 @@ test "mmap" {
450243
451 // Map the upper half of the file244 // Map the upper half of the file
452 {245 {
453 const file = try tmp.dir.openFile(test_out_file, .{});246 const file = try tmp.dir.openFile(io, test_out_file, .{});
454 defer file.close();247 defer file.close(io);
455248
456 const data = try posix.mmap(249 const data = try posix.mmap(
457 null,250 null,
...@@ -467,7 +260,7 @@ test "mmap" {...@@ -467,7 +260,7 @@ test "mmap" {
467260
468 var i: usize = alloc_size / 2 / @sizeOf(u32);261 var i: usize = alloc_size / 2 / @sizeOf(u32);
469 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {262 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));
471 }264 }
472 }265 }
473}266}
...@@ -476,13 +269,15 @@ test "fcntl" {...@@ -476,13 +269,15 @@ test "fcntl" {
476 if (native_os == .windows or native_os == .wasi)269 if (native_os == .windows or native_os == .wasi)
477 return error.SkipZigTest;270 return error.SkipZigTest;
478271
272 const io = testing.io;
273
479 var tmp = tmpDir(.{});274 var tmp = tmpDir(.{});
480 defer tmp.cleanup();275 defer tmp.cleanup();
481276
482 const test_out_file = "os_tmp_test";277 const test_out_file = "os_tmp_test";
483278
484 const file = try tmp.dir.createFile(test_out_file, .{});279 const file = try tmp.dir.createFile(io, test_out_file, .{});
485 defer file.close();280 defer file.close(io);
486281
487 // Note: The test assumes createFile opens the file with CLOEXEC282 // Note: The test assumes createFile opens the file with CLOEXEC
488 {283 {
...@@ -522,18 +317,20 @@ test "sync" {...@@ -522,18 +317,20 @@ test "sync" {
522317
523test "fsync" {318test "fsync" {
524 switch (native_os) {319 switch (native_os) {
525 .linux, .windows, .illumos => {},320 .linux, .illumos => {},
526 else => return error.SkipZigTest,321 else => return error.SkipZigTest,
527 }322 }
528323
324 const io = testing.io;
325
529 var tmp = tmpDir(.{});326 var tmp = tmpDir(.{});
530 defer tmp.cleanup();327 defer tmp.cleanup();
531328
532 const test_out_file = "os_tmp_test";329 const test_out_file = "os_tmp_test";
533 const file = try tmp.dir.createFile(test_out_file, .{});330 const file = try tmp.dir.createFile(io, test_out_file, .{});
534 defer file.close();331 defer file.close(io);
535332
536 try posix.fsync(file.handle);333 try file.sync(io);
537 try posix.fdatasync(file.handle);334 try posix.fdatasync(file.handle);
538}335}
539336
...@@ -571,9 +368,9 @@ test "sigrtmin/max" {...@@ -571,9 +368,9 @@ test "sigrtmin/max" {
571 return error.SkipZigTest;368 return error.SkipZigTest;
572 }369 }
573370
574 try std.testing.expect(posix.sigrtmin() >= 32);371 try expect(posix.sigrtmin() >= 32);
575 try std.testing.expect(posix.sigrtmin() >= posix.system.sigrtmin());372 try expect(posix.sigrtmin() >= posix.system.sigrtmin());
576 try std.testing.expect(posix.sigrtmin() < posix.system.sigrtmax());373 try expect(posix.sigrtmin() < posix.system.sigrtmax());
577}374}
578375
579test "sigset empty/full" {376test "sigset empty/full" {
...@@ -646,27 +443,29 @@ test "dup & dup2" {...@@ -646,27 +443,29 @@ test "dup & dup2" {
646 else => return error.SkipZigTest,443 else => return error.SkipZigTest,
647 }444 }
648445
446 const io = testing.io;
447
649 var tmp = tmpDir(.{});448 var tmp = tmpDir(.{});
650 defer tmp.cleanup();449 defer tmp.cleanup();
651450
652 {451 {
653 var file = try tmp.dir.createFile("os_dup_test", .{});452 var file = try tmp.dir.createFile(io, "os_dup_test", .{});
654 defer file.close();453 defer file.close(io);
655454
656 var duped = std.fs.File{ .handle = try posix.dup(file.handle) };455 var duped = Io.File{ .handle = try posix.dup(file.handle) };
657 defer duped.close();456 defer duped.close(io);
658 try duped.writeAll("dup");457 try duped.writeStreamingAll(io, "dup");
659458
660 // Tests aren't run in parallel so using the next fd shouldn't be an issue.459 // Tests aren't run in parallel so using the next fd shouldn't be an issue.
661 const new_fd = duped.handle + 1;460 const new_fd = duped.handle + 1;
662 try posix.dup2(file.handle, new_fd);461 try posix.dup2(file.handle, new_fd);
663 var dup2ed = std.fs.File{ .handle = new_fd };462 var dup2ed = Io.File{ .handle = new_fd };
664 defer dup2ed.close();463 defer dup2ed.close(io);
665 try dup2ed.writeAll("dup2");464 try dup2ed.writeStreamingAll(io, "dup2");
666 }465 }
667466
668 var buffer: [8]u8 = undefined;467 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));
670}469}
671470
672test "getpid" {471test "getpid" {
...@@ -684,208 +483,78 @@ test "getppid" {...@@ -684,208 +483,78 @@ test "getppid" {
684 try expect(posix.getppid() >= 0);483 try expect(posix.getppid() >= 0);
685}484}
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
766test "rename smoke test" {486test "rename smoke test" {
767 if (native_os == .wasi) return error.SkipZigTest;487 if (native_os == .wasi) return error.SkipZigTest;
768 if (native_os == .windows) return error.SkipZigTest;488 if (native_os == .windows) return error.SkipZigTest;
769 if (native_os == .openbsd) return error.SkipZigTest;489 if (native_os == .openbsd) return error.SkipZigTest;
770490
491 const io = testing.io;
492 const gpa = testing.allocator;
493
771 var tmp = tmpDir(.{});494 var tmp = tmpDir(.{});
772 defer tmp.cleanup();495 defer tmp.cleanup();
773496
774 const base_path = try tmp.dir.realpathAlloc(a, ".");497 const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
775 defer a.free(base_path);498 defer gpa.free(base_path);
776499
777 const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;500 const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
778501
779 {502 {
780 // Create some file using `open`.503 // Create some file using `open`.
781 const file_path = try fs.path.join(a, &.{ base_path, "some_file" });504 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" });
782 defer a.free(file_path);505 defer gpa.free(file_path);
783 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);506 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
784 posix.close(fd);507 posix.close(fd);
785508
786 // Rename the file509 // Rename the file
787 const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_file" });510 const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" });
788 defer a.free(new_file_path);511 defer gpa.free(new_file_path);
789 try posix.rename(file_path, new_file_path);512 try Io.Dir.renameAbsolute(file_path, new_file_path, io);
790 }513 }
791514
792 {515 {
793 // Try opening renamed file516 // Try opening renamed file
794 const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" });517 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" });
795 defer a.free(file_path);518 defer gpa.free(file_path);
796 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode);519 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode);
797 posix.close(fd);520 posix.close(fd);
798 }521 }
799522
800 {523 {
801 // Try opening original file - should fail with error.FileNotFound524 // Try opening original file - should fail with error.FileNotFound
802 const file_path = try fs.path.join(a, &.{ base_path, "some_file" });525 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" });
803 defer a.free(file_path);526 defer gpa.free(file_path);
804 try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));527 try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));
805 }528 }
806529
807 {530 {
808 // Create some directory531 // Create some directory
809 const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });532 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });
810 defer a.free(file_path);533 defer gpa.free(file_path);
811 try posix.mkdir(file_path, mode);534 try posix.mkdir(file_path, mode);
812535
813 // Rename the directory536 // Rename the directory
814 const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" });537 const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });
815 defer a.free(new_file_path);538 defer gpa.free(new_file_path);
816 try posix.rename(file_path, new_file_path);539 try Io.Dir.renameAbsolute(file_path, new_file_path, io);
817 }540 }
818541
819 {542 {
820 // Try opening renamed directory543 // Try opening renamed directory
821 const file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" });544 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });
822 defer a.free(file_path);545 defer gpa.free(file_path);
823 const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);546 const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);
824 posix.close(fd);547 posix.close(fd);
825 }548 }
826549
827 {550 {
828 // Try opening original directory - should fail with error.FileNotFound551 // Try opening original directory - should fail with error.FileNotFound
829 const file_path = try fs.path.join(a, &.{ base_path, "some_dir" });552 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });
830 defer a.free(file_path);553 defer gpa.free(file_path);
831 try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode));554 try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode));
832 }555 }
833}556}
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
889test "timerfd" {558test "timerfd" {
890 if (native_os != .linux) return error.SkipZigTest;559 if (native_os != .linux) return error.SkipZigTest;
891560
...@@ -903,142 +572,3 @@ test "timerfd" {...@@ -903,142 +572,3 @@ test "timerfd" {
903 const expect_disarmed_timer: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 0 } };572 const expect_disarmed_timer: linux.itimerspec = .{ .it_interval = .{ .sec = 0, .nsec = 0 }, .it_value = .{ .sec = 0, .nsec = 0 } };
904 try expectEqual(expect_disarmed_timer, git);573 try expectEqual(expect_disarmed_timer, git);
905}574}
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 @@...@@ -1,24 +1,33 @@
1const std = @import("std.zig");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3
4const std = @import("std.zig");
5const Io = std.Io;
6const File = std.Io.File;
3const fs = std.fs;7const fs = std.fs;
4const mem = std.mem;8const mem = std.mem;
5const math = std.math;9const math = std.math;
6const Allocator = mem.Allocator;10const Allocator = std.mem.Allocator;
7const assert = std.debug.assert;11const assert = std.debug.assert;
8const testing = std.testing;12const testing = std.testing;
9const native_os = builtin.os.tag;
10const posix = std.posix;13const posix = std.posix;
11const windows = std.os.windows;14const windows = std.os.windows;
12const unicode = std.unicode;15const unicode = std.unicode;
16const max_path_bytes = std.fs.max_path_bytes;
1317
14pub const Child = @import("process/Child.zig");18pub const Child = @import("process/Child.zig");
15pub const abort = posix.abort;
16pub const exit = posix.exit;
17pub const changeCurDir = posix.chdir;19pub const changeCurDir = posix.chdir;
18pub const changeCurDirZ = posix.chdirZ;20pub const changeCurDirZ = posix.chdirZ;
1921
20pub const GetCwdError = posix.GetCwdError;22pub 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
22/// The result is a slice of `out_buffer`, from index `0`.31/// The result is a slice of `out_buffer`, from index `0`.
23/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).32/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
24/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.33/// 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...@@ -35,7 +44,7 @@ pub const GetCwdAllocError = Allocator.Error || error{CurrentWorkingDirectoryUnl
35pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 {44pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 {
36 // The use of max_path_bytes here is just a heuristic: most paths will fit45 // The use of max_path_bytes here is just a heuristic: most paths will fit
37 // in stack_buf, avoiding an extra allocation in the common case.46 // 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;
39 var heap_buf: ?[]u8 = null;48 var heap_buf: ?[]u8 = null;
40 defer if (heap_buf) |buf| allocator.free(buf);49 defer if (heap_buf) |buf| allocator.free(buf);
4150
...@@ -437,25 +446,25 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError...@@ -437,25 +446,25 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
437}446}
438447
439/// On Windows, `key` must be valid WTF-8.448/// 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 {
441 if (native_os == .windows) {450 if (native_os == .windows) {
442 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);451 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
443 return getenvW(key_w) != null;452 return getenvW(key_w) != null;
444 } else if (native_os == .wasi and !builtin.link_libc) {453 } else if (native_os == .wasi and !builtin.link_libc) {
445 @compileError("hasEnvVarConstant is not supported for WASI without libc");454 return false;
446 } else {455 } else {
447 return posix.getenv(key) != null;456 return posix.getenv(key) != null;
448 }457 }
449}458}
450459
451/// On Windows, `key` must be valid WTF-8.460/// 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 {
453 if (native_os == .windows) {462 if (native_os == .windows) {
454 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);463 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
455 const value = getenvW(key_w) orelse return false;464 const value = getenvW(key_w) orelse return false;
456 return value.len != 0;465 return value.len != 0;
457 } else if (native_os == .wasi and !builtin.link_libc) {466 } else if (native_os == .wasi and !builtin.link_libc) {
458 @compileError("hasNonEmptyEnvVarConstant is not supported for WASI without libc");467 return false;
459 } else {468 } else {
460 const value = posix.getenv(key) orelse return false;469 const value = posix.getenv(key) orelse return false;
461 return value.len != 0;470 return value.len != 0;
...@@ -1571,9 +1580,9 @@ pub fn getUserInfo(name: []const u8) !UserInfo {...@@ -1571,9 +1580,9 @@ pub fn getUserInfo(name: []const u8) !UserInfo {
15711580
1572/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else1581/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
1573/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.1582/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
1574pub fn posixGetUserInfo(name: []const u8) !UserInfo {1583pub fn posixGetUserInfo(io: Io, name: []const u8) !UserInfo {
1575 const file = try std.fs.openFileAbsolute("/etc/passwd", .{});1584 const file = try Io.Dir.openFileAbsolute(io, "/etc/passwd", .{});
1576 defer file.close();1585 defer file.close(io);
1577 var buffer: [4096]u8 = undefined;1586 var buffer: [4096]u8 = undefined;
1578 var file_reader = file.reader(&buffer);1587 var file_reader = file.reader(&buffer);
1579 return posixGetUserInfoPasswdStream(name, &file_reader.interface) catch |err| switch (err) {1588 return posixGetUserInfoPasswdStream(name, &file_reader.interface) catch |err| switch (err) {
...@@ -1839,21 +1848,19 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 {...@@ -1839,21 +1848,19 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 {
1839 }1848 }
1840}1849}
18411850
1842/// Indicate that we are now terminating with a successful exit code.1851/// Indicate intent to terminate with a successful exit code.
1843/// In debug builds, this is a no-op, so that the calling code's1852///
1844/// cleanup mechanisms are tested and so that external tools that1853/// In debug builds, this is a no-op, so that the calling code's cleanup
1845/// check for resource leaks can be accurate. In release builds, this1854/// mechanisms are tested and so that external tools checking for resource
1846/// calls exit(0), and does not return.1855/// leaks can be accurate. In release builds, this calls `exit` with code zero,
1847pub fn cleanExit() void {1856/// and does not return.
1848 if (builtin.mode == .Debug) {1857pub fn cleanExit(io: Io) void {
1849 return;1858 if (builtin.mode == .Debug) return;
1850 } else {1859 _ = io.lockStderr(&.{}, .no_color) catch {};
1851 std.debug.lockStdErr();1860 exit(0);
1852 exit(0);
1853 }
1854}1861}
18551862
1856/// Raise the open file descriptor limit.1863/// Request ability to have more open file descriptors simultaneously.
1857///1864///
1858/// On some systems, this raises the limit before seeing ProcessFdQuotaExceeded1865/// On some systems, this raises the limit before seeing ProcessFdQuotaExceeded
1859/// errors. On other systems, this does nothing.1866/// errors. On other systems, this does nothing.
...@@ -2110,3 +2117,215 @@ pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {...@@ -2110,3 +2117,215 @@ pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {
2110 std.log.err(format, format_arguments);2117 std.log.err(format, format_arguments);
2111 exit(1);2118 exit(1);
2112}2119}
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 @@...@@ -1,13 +1,14 @@
1const ChildProcess = @This();1const Child = @This();
22
3const builtin = @import("builtin");3const builtin = @import("builtin");
4const native_os = builtin.os.tag;4const native_os = builtin.os.tag;
55
6const std = @import("../std.zig");6const std = @import("../std.zig");
7const Io = std.Io;
7const unicode = std.unicode;8const unicode = std.unicode;
8const fs = std.fs;9const fs = std.fs;
9const process = std.process;10const process = std.process;
10const File = std.fs.File;11const File = std.Io.File;
11const windows = std.os.windows;12const windows = std.os.windows;
12const linux = std.os.linux;13const linux = std.os.linux;
13const posix = std.posix;14const posix = std.posix;
...@@ -30,7 +31,7 @@ pub const Id = switch (native_os) {...@@ -30,7 +31,7 @@ pub const Id = switch (native_os) {
30id: Id,31id: Id,
31thread_handle: if (native_os == .windows) windows.HANDLE else void,32thread_handle: if (native_os == .windows) windows.HANDLE else void,
3233
33allocator: mem.Allocator,34allocator: Allocator,
3435
35/// The writing end of the child process's standard input pipe.36/// The writing end of the child process's standard input pipe.
36/// Usage requires `stdin_behavior == StdIo.Pipe`.37/// Usage requires `stdin_behavior == StdIo.Pipe`.
...@@ -76,7 +77,7 @@ cwd: ?[]const u8,...@@ -76,7 +77,7 @@ cwd: ?[]const u8,
76/// Set to change the current working directory when spawning the child process.77/// Set to change the current working directory when spawning the child process.
77/// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/519078/// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
78/// Once that is done, `cwd` will be deprecated in favor of this field.79/// Once that is done, `cwd` will be deprecated in favor of this field.
79cwd_dir: ?fs.Dir = null,80cwd_dir: ?Io.Dir = null,
8081
81err_pipe: if (native_os == .windows) void else ?posix.fd_t,82err_pipe: if (native_os == .windows) void else ?posix.fd_t,
8283
...@@ -228,7 +229,7 @@ pub const StdIo = enum {...@@ -228,7 +229,7 @@ pub const StdIo = enum {
228};229};
229230
230/// First argument in argv is the executable.231/// 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 {
232 return .{233 return .{
233 .allocator = allocator,234 .allocator = allocator,
234 .argv = argv,235 .argv = argv,
...@@ -251,7 +252,7 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {...@@ -251,7 +252,7 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
251 };252 };
252}253}
253254
254pub fn setUserName(self: *ChildProcess, name: []const u8) !void {255pub fn setUserName(self: *Child, name: []const u8) !void {
255 const user_info = try process.getUserInfo(name);256 const user_info = try process.getUserInfo(name);
256 self.uid = user_info.uid;257 self.uid = user_info.uid;
257 self.gid = user_info.gid;258 self.gid = user_info.gid;
...@@ -259,35 +260,35 @@ pub fn setUserName(self: *ChildProcess, name: []const u8) !void {...@@ -259,35 +260,35 @@ pub fn setUserName(self: *ChildProcess, name: []const u8) !void {
259260
260/// On success must call `kill` or `wait`.261/// On success must call `kill` or `wait`.
261/// After spawning the `id` is available.262/// After spawning the `id` is available.
262pub fn spawn(self: *ChildProcess) SpawnError!void {263pub fn spawn(self: *Child, io: Io) SpawnError!void {
263 if (!process.can_spawn) {264 if (!process.can_spawn) {
264 @compileError("the target operating system cannot spawn processes");265 @compileError("the target operating system cannot spawn processes");
265 }266 }
266267
267 if (native_os == .windows) {268 if (native_os == .windows) {
268 return self.spawnWindows();269 return self.spawnWindows(io);
269 } else {270 } else {
270 return self.spawnPosix();271 return self.spawnPosix(io);
271 }272 }
272}273}
273274
274pub fn spawnAndWait(self: *ChildProcess) SpawnError!Term {275pub fn spawnAndWait(child: *Child, io: Io) SpawnError!Term {
275 try self.spawn();276 try child.spawn(io);
276 return self.wait();277 return child.wait(io);
277}278}
278279
279/// Forcibly terminates child process and then cleans up all resources.280/// Forcibly terminates child process and then cleans up all resources.
280pub fn kill(self: *ChildProcess) !Term {281pub fn kill(self: *Child, io: Io) !Term {
281 if (native_os == .windows) {282 if (native_os == .windows) {
282 return self.killWindows(1);283 return self.killWindows(io, 1);
283 } else {284 } else {
284 return self.killPosix();285 return self.killPosix(io);
285 }286 }
286}287}
287288
288pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term {289pub fn killWindows(self: *Child, io: Io, exit_code: windows.UINT) !Term {
289 if (self.term) |term| {290 if (self.term) |term| {
290 self.cleanupStreams();291 self.cleanupStreams(io);
291 return term;292 return term;
292 }293 }
293294
...@@ -303,20 +304,20 @@ pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term {...@@ -303,20 +304,20 @@ pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term {
303 },304 },
304 else => return err,305 else => return err,
305 };306 };
306 try self.waitUnwrappedWindows();307 try self.waitUnwrappedWindows(io);
307 return self.term.?;308 return self.term.?;
308}309}
309310
310pub fn killPosix(self: *ChildProcess) !Term {311pub fn killPosix(self: *Child, io: Io) !Term {
311 if (self.term) |term| {312 if (self.term) |term| {
312 self.cleanupStreams();313 self.cleanupStreams(io);
313 return term;314 return term;
314 }315 }
315 posix.kill(self.id, posix.SIG.TERM) catch |err| switch (err) {316 posix.kill(self.id, posix.SIG.TERM) catch |err| switch (err) {
316 error.ProcessNotFound => return error.AlreadyTerminated,317 error.ProcessNotFound => return error.AlreadyTerminated,
317 else => return err,318 else => return err,
318 };319 };
319 self.waitUnwrappedPosix();320 self.waitUnwrappedPosix(io);
320 return self.term.?;321 return self.term.?;
321}322}
322323
...@@ -324,7 +325,7 @@ pub const WaitError = SpawnError || std.os.windows.GetProcessMemoryInfoError;...@@ -324,7 +325,7 @@ pub const WaitError = SpawnError || std.os.windows.GetProcessMemoryInfoError;
324325
325/// On some targets, `spawn` may not report all spawn errors, such as `error.InvalidExe`.326/// On some targets, `spawn` may not report all spawn errors, such as `error.InvalidExe`.
326/// This function will block until any spawn errors can be reported, and return them.327/// 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 {
328 if (native_os == .windows) return; // `spawn` reports everything329 if (native_os == .windows) return; // `spawn` reports everything
329 if (self.term) |term| {330 if (self.term) |term| {
330 _ = term catch |spawn_err| return spawn_err;331 _ = term catch |spawn_err| return spawn_err;
...@@ -354,15 +355,15 @@ pub fn waitForSpawn(self: *ChildProcess) SpawnError!void {...@@ -354,15 +355,15 @@ pub fn waitForSpawn(self: *ChildProcess) SpawnError!void {
354}355}
355356
356/// Blocks until child process terminates and then cleans up all resources.357/// 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 {
358 try self.waitForSpawn(); // report spawn errors359 try self.waitForSpawn(); // report spawn errors
359 if (self.term) |term| {360 if (self.term) |term| {
360 self.cleanupStreams();361 self.cleanupStreams(io);
361 return term;362 return term;
362 }363 }
363 switch (native_os) {364 switch (native_os) {
364 .windows => try self.waitUnwrappedWindows(),365 .windows => try self.waitUnwrappedWindows(io),
365 else => self.waitUnwrappedPosix(),366 else => self.waitUnwrappedPosix(io),
366 }367 }
367 self.id = undefined;368 self.id = undefined;
368 return self.term.?;369 return self.term.?;
...@@ -380,7 +381,7 @@ pub const RunResult = struct {...@@ -380,7 +381,7 @@ pub const RunResult = struct {
380///381///
381/// The process must be started with stdout_behavior and stderr_behavior == .Pipe382/// The process must be started with stdout_behavior and stderr_behavior == .Pipe
382pub fn collectOutput(383pub fn collectOutput(
383 child: ChildProcess,384 child: Child,
384 /// Used for `stdout` and `stderr`.385 /// Used for `stdout` and `stderr`.
385 allocator: Allocator,386 allocator: Allocator,
386 stdout: *ArrayList(u8),387 stdout: *ArrayList(u8),
...@@ -434,11 +435,10 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix...@@ -434,11 +435,10 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix
434435
435/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.436/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
436/// If it succeeds, the caller owns result.stdout and result.stderr memory.437/// If it succeeds, the caller owns result.stdout and result.stderr memory.
437pub fn run(args: struct {438pub fn run(allocator: Allocator, io: Io, args: struct {
438 allocator: mem.Allocator,
439 argv: []const []const u8,439 argv: []const []const u8,
440 cwd: ?[]const u8 = null,440 cwd: ?[]const u8 = null,
441 cwd_dir: ?fs.Dir = null,441 cwd_dir: ?Io.Dir = null,
442 /// Required if unable to access the current env map (e.g. building a442 /// Required if unable to access the current env map (e.g. building a
443 /// library on some platforms).443 /// library on some platforms).
444 env_map: ?*const EnvMap = null,444 env_map: ?*const EnvMap = null,
...@@ -446,7 +446,7 @@ pub fn run(args: struct {...@@ -446,7 +446,7 @@ pub fn run(args: struct {
446 expand_arg0: Arg0Expand = .no_expand,446 expand_arg0: Arg0Expand = .no_expand,
447 progress_node: std.Progress.Node = std.Progress.Node.none,447 progress_node: std.Progress.Node = std.Progress.Node.none,
448}) RunError!RunResult {448}) RunError!RunResult {
449 var child = ChildProcess.init(args.argv, args.allocator);449 var child = Child.init(args.argv, allocator);
450 child.stdin_behavior = .Ignore;450 child.stdin_behavior = .Ignore;
451 child.stdout_behavior = .Pipe;451 child.stdout_behavior = .Pipe;
452 child.stderr_behavior = .Pipe;452 child.stderr_behavior = .Pipe;
...@@ -457,24 +457,24 @@ pub fn run(args: struct {...@@ -457,24 +457,24 @@ pub fn run(args: struct {
457 child.progress_node = args.progress_node;457 child.progress_node = args.progress_node;
458458
459 var stdout: ArrayList(u8) = .empty;459 var stdout: ArrayList(u8) = .empty;
460 defer stdout.deinit(args.allocator);460 defer stdout.deinit(allocator);
461 var stderr: ArrayList(u8) = .empty;461 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);
465 errdefer {465 errdefer {
466 _ = child.kill() catch {};466 _ = child.kill(io) catch {};
467 }467 }
468 try child.collectOutput(args.allocator, &stdout, &stderr, args.max_output_bytes);468 try child.collectOutput(allocator, &stdout, &stderr, args.max_output_bytes);
469469
470 return .{470 return .{
471 .stdout = try stdout.toOwnedSlice(args.allocator),471 .stdout = try stdout.toOwnedSlice(allocator),
472 .stderr = try stderr.toOwnedSlice(args.allocator),472 .stderr = try stderr.toOwnedSlice(allocator),
473 .term = try child.wait(),473 .term = try child.wait(io),
474 };474 };
475}475}
476476
477fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void {477fn waitUnwrappedWindows(self: *Child, io: Io) WaitError!void {
478 const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false);478 const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false);
479479
480 self.term = @as(SpawnError!Term, x: {480 self.term = @as(SpawnError!Term, x: {
...@@ -492,11 +492,11 @@ fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void {...@@ -492,11 +492,11 @@ fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void {
492492
493 posix.close(self.id);493 posix.close(self.id);
494 posix.close(self.thread_handle);494 posix.close(self.thread_handle);
495 self.cleanupStreams();495 self.cleanupStreams(io);
496 return result;496 return result;
497}497}
498498
499fn waitUnwrappedPosix(self: *ChildProcess) void {499fn waitUnwrappedPosix(self: *Child, io: Io) void {
500 const res: posix.WaitPidResult = res: {500 const res: posix.WaitPidResult = res: {
501 if (self.request_resource_usage_statistics) {501 if (self.request_resource_usage_statistics) {
502 switch (native_os) {502 switch (native_os) {
...@@ -527,25 +527,25 @@ fn waitUnwrappedPosix(self: *ChildProcess) void {...@@ -527,25 +527,25 @@ fn waitUnwrappedPosix(self: *ChildProcess) void {
527 break :res posix.waitpid(self.id, 0);527 break :res posix.waitpid(self.id, 0);
528 };528 };
529 const status = res.status;529 const status = res.status;
530 self.cleanupStreams();530 self.cleanupStreams(io);
531 self.handleWaitResult(status);531 self.handleWaitResult(status);
532}532}
533533
534fn handleWaitResult(self: *ChildProcess, status: u32) void {534fn handleWaitResult(self: *Child, status: u32) void {
535 self.term = statusToTerm(status);535 self.term = statusToTerm(status);
536}536}
537537
538fn cleanupStreams(self: *ChildProcess) void {538fn cleanupStreams(self: *Child, io: Io) void {
539 if (self.stdin) |*stdin| {539 if (self.stdin) |*stdin| {
540 stdin.close();540 stdin.close(io);
541 self.stdin = null;541 self.stdin = null;
542 }542 }
543 if (self.stdout) |*stdout| {543 if (self.stdout) |*stdout| {
544 stdout.close();544 stdout.close(io);
545 self.stdout = null;545 self.stdout = null;
546 }546 }
547 if (self.stderr) |*stderr| {547 if (self.stderr) |*stderr| {
548 stderr.close();548 stderr.close(io);
549 self.stderr = null;549 self.stderr = null;
550 }550 }
551}551}
...@@ -561,7 +561,7 @@ fn statusToTerm(status: u32) Term {...@@ -561,7 +561,7 @@ fn statusToTerm(status: u32) Term {
561 Term{ .Unknown = status };561 Term{ .Unknown = status };
562}562}
563563
564fn spawnPosix(self: *ChildProcess) SpawnError!void {564fn spawnPosix(self: *Child, io: Io) SpawnError!void {
565 // The child process does need to access (one end of) these pipes. However,565 // The child process does need to access (one end of) these pipes. However,
566 // we must initially set CLOEXEC to avoid a race condition. If another thread566 // we must initially set CLOEXEC to avoid a race condition. If another thread
567 // is racing to spawn a different child process, we don't want it to inherit567 // 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 {...@@ -596,7 +596,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
596 error.NoSpaceLeft => unreachable,596 error.NoSpaceLeft => unreachable,
597 error.FileTooBig => unreachable,597 error.FileTooBig => unreachable,
598 error.DeviceBusy => unreachable,598 error.DeviceBusy => unreachable,
599 error.FileLocksNotSupported => unreachable,599 error.FileLocksUnsupported => unreachable,
600 error.BadPathName => unreachable, // Windows-only600 error.BadPathName => unreachable, // Windows-only
601 error.WouldBlock => unreachable,601 error.WouldBlock => unreachable,
602 error.NetworkNotFound => unreachable, // Windows-only602 error.NetworkNotFound => unreachable, // Windows-only
...@@ -659,7 +659,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -659,7 +659,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
659 })).ptr;659 })).ptr;
660 } else {660 } else {
661 // TODO come up with a solution for this.661 // 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");
663 }663 }
664 };664 };
665665
...@@ -671,41 +671,41 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -671,41 +671,41 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
671 const pid_result = try posix.fork();671 const pid_result = try posix.fork();
672 if (pid_result == 0) {672 if (pid_result == 0) {
673 // we are the child673 // 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);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(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(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
678 if (self.cwd_dir) |cwd| {678 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);
680 } else if (self.cwd) |cwd| {680 } 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);
682 }682 }
683683
684 // Must happen after fchdir above, the cwd file descriptor might be684 // Must happen after fchdir above, the cwd file descriptor might be
685 // equal to prog_fileno and be clobbered by this dup2 call.685 // 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
688 if (self.gid) |gid| {688 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);
690 }690 }
691691
692 if (self.uid) |uid| {692 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);
694 }694 }
695695
696 if (self.pgid) |pid| {696 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);
698 }698 }
699699
700 if (self.start_suspended) {700 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);
702 }702 }
703703
704 const err = switch (self.expand_arg0) {704 const err = switch (self.expand_arg0) {
705 .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),705 .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),
706 .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),706 .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),
707 };707 };
708 forkChildErrReport(err_pipe[1], err);708 forkChildErrReport(io, err_pipe[1], err);
709 }709 }
710710
711 // we are the parent711 // we are the parent
...@@ -750,7 +750,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -750,7 +750,7 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
750 self.progress_node.setIpcFd(prog_pipe[0]);750 self.progress_node.setIpcFd(prog_pipe[0]);
751}751}
752752
753fn spawnWindows(self: *ChildProcess) SpawnError!void {753fn spawnWindows(self: *Child, io: Io) SpawnError!void {
754 var saAttr = windows.SECURITY_ATTRIBUTES{754 var saAttr = windows.SECURITY_ATTRIBUTES{
755 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),755 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
756 .bInheritHandle = windows.TRUE,756 .bInheritHandle = windows.TRUE,
...@@ -880,7 +880,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {...@@ -880,7 +880,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
880 const app_name_wtf8 = self.argv[0];880 const app_name_wtf8 = self.argv[0];
881 const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);881 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 path883 // the cwd set in Child is in effect when choosing the executable path
884 // to match posix semantics884 // to match posix semantics
885 var cwd_path_w_needs_free = false;885 var cwd_path_w_needs_free = false;
886 const cwd_path_w = x: {886 const cwd_path_w = x: {
...@@ -953,7 +953,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {...@@ -953,7 +953,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
953 try dir_buf.appendSlice(self.allocator, app_dir);953 try dir_buf.appendSlice(self.allocator, app_dir);
954 }954 }
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| {
957 const original_err = switch (no_path_err) {957 const original_err = switch (no_path_err) {
958 // argv[0] contains unsupported characters that will never resolve to a valid exe.958 // argv[0] contains unsupported characters that will never resolve to a valid exe.
959 error.InvalidArg0 => return error.FileNotFound,959 error.InvalidArg0 => return error.FileNotFound,
...@@ -965,7 +965,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {...@@ -965,7 +965,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
965 // If the app name had path separators, that disallows PATH searching,965 // If the app name had path separators, that disallows PATH searching,
966 // and there's no need to search the PATH if the app name is absolute.966 // and there's no need to search the PATH if the app name is absolute.
967 // We still search the path if the cwd is absolute because of the967 // 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 path968 // "cwd set in Child is in effect when choosing the executable path
969 // to match posix semantics" behavior--we don't want to skip searching969 // to match posix semantics" behavior--we don't want to skip searching
970 // the PATH just because we were trying to set the cwd of the child process.970 // the PATH just because we were trying to set the cwd of the child process.
971 if (app_dirname_w != null or app_name_is_absolute) {971 if (app_dirname_w != null or app_name_is_absolute) {
...@@ -977,7 +977,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {...@@ -977,7 +977,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
977 dir_buf.clearRetainingCapacity();977 dir_buf.clearRetainingCapacity();
978 try dir_buf.appendSlice(self.allocator, search_path);978 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)) {
981 break :run;981 break :run;
982 } else |err| switch (err) {982 } else |err| switch (err) {
983 // argv[0] contains unsupported characters that will never resolve to a valid exe.983 // 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 {...@@ -1039,8 +1039,8 @@ fn destroyPipe(pipe: [2]posix.fd_t) void {
10391039
1040// Child of fork calls this to report an error to the fork parent.1040// Child of fork calls this to report an error to the fork parent.
1041// Then the child exits.1041// Then the child exits.
1042fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {1042fn forkChildErrReport(io: Io, fd: i32, err: Child.SpawnError) noreturn {
1043 writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {};1043 writeIntFd(io, fd, @as(ErrInt, @intFromError(err))) catch {};
1044 // If we're linking libc, some naughty applications may have registered atexit handlers1044 // If we're linking libc, some naughty applications may have registered atexit handlers
1045 // which we really do not want to run in the fork child. I caught LLVM doing this and1045 // which we really do not want to run in the fork child. I caught LLVM doing this and
1046 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,1046 // 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 {...@@ -1049,12 +1049,12 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
1049 // The _exit(2) function does nothing but make the exit syscall, unlike exit(3)1049 // The _exit(2) function does nothing but make the exit syscall, unlike exit(3)
1050 std.c._exit(1);1050 std.c._exit(1);
1051 }1051 }
1052 posix.exit(1);1052 posix.system.exit(1);
1053}1053}
10541054
1055fn writeIntFd(fd: i32, value: ErrInt) !void {1055fn writeIntFd(io: Io, fd: i32, value: ErrInt) !void {
1056 var buffer: [8]u8 = undefined;1056 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);
1058 fw.interface.writeInt(u64, value, .little) catch unreachable;1058 fw.interface.writeInt(u64, value, .little) catch unreachable;
1059 fw.interface.flush() catch return error.SystemResources;1059 fw.interface.flush() catch return error.SystemResources;
1060}1060}
...@@ -1078,7 +1078,8 @@ const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);...@@ -1078,7 +1078,8 @@ const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
1078/// Note: `app_buf` should not contain any leading path separators.1078/// Note: `app_buf` should not contain any leading path separators.
1079/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).1079/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
1080fn windowsCreateProcessPathExt(1080fn windowsCreateProcessPathExt(
1081 allocator: mem.Allocator,1081 allocator: Allocator,
1082 io: Io,
1082 dir_buf: *ArrayList(u16),1083 dir_buf: *ArrayList(u16),
1083 app_buf: *ArrayList(u16),1084 app_buf: *ArrayList(u16),
1084 pathext: [:0]const u16,1085 pathext: [:0]const u16,
...@@ -1122,16 +1123,14 @@ fn windowsCreateProcessPathExt(...@@ -1122,16 +1123,14 @@ fn windowsCreateProcessPathExt(
1122 // Under those conditions, here we will have access to lower level directory1123 // Under those conditions, here we will have access to lower level directory
1123 // opening function knowing which implementation we are in. Here, we imitate1124 // opening function knowing which implementation we are in. Here, we imitate
1124 // that scenario.1125 // that scenario.
1125 var threaded: std.Io.Threaded = .init_single_threaded;
1126 const io = threaded.ioBasic();
1127
1128 var dir = dir: {1126 var dir = dir: {
1129 // needs to be null-terminated1127 // needs to be null-terminated
1130 try dir_buf.append(allocator, 0);1128 try dir_buf.append(allocator, 0);
1131 defer dir_buf.shrinkRetainingCapacity(dir_path_len);1129 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
1132 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];1130 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1133 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);1131 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(), .{
1135 .iterate = true,1134 .iterate = true,
1136 }) catch return error.FileNotFound;1135 }) catch return error.FileNotFound;
1137 };1136 };
...@@ -1525,9 +1524,9 @@ const WindowsCommandLineCache = struct {...@@ -1525,9 +1524,9 @@ const WindowsCommandLineCache = struct {
1525 script_cmd_line: ?[:0]u16 = null,1524 script_cmd_line: ?[:0]u16 = null,
1526 cmd_exe_path: ?[:0]u16 = null,1525 cmd_exe_path: ?[:0]u16 = null,
1527 argv: []const []const u8,1526 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 {
1531 return .{1530 return .{
1532 .allocator = allocator,1531 .allocator = allocator,
1533 .argv = argv,1532 .argv = argv,
...@@ -1571,7 +1570,7 @@ const WindowsCommandLineCache = struct {...@@ -1571,7 +1570,7 @@ const WindowsCommandLineCache = struct {
15711570
1572/// Returns the absolute path of `cmd.exe` within the Windows system directory.1571/// Returns the absolute path of `cmd.exe` within the Windows system directory.
1573/// The caller owns the returned slice.1572/// 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 {
1575 var buf = try ArrayList(u16).initCapacity(allocator, 128);1574 var buf = try ArrayList(u16).initCapacity(allocator, 128);
1576 errdefer buf.deinit(allocator);1575 errdefer buf.deinit(allocator);
1577 while (true) {1576 while (true) {
...@@ -1608,7 +1607,7 @@ const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };...@@ -1608,7 +1607,7 @@ const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
1608///1607///
1609/// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead.1608/// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead.
1610fn argvToCommandLineWindows(1609fn argvToCommandLineWindows(
1611 allocator: mem.Allocator,1610 allocator: Allocator,
1612 argv: []const []const u8,1611 argv: []const []const u8,
1613) ArgvToCommandLineError![:0]u16 {1612) ArgvToCommandLineError![:0]u16 {
1614 var buf = std.array_list.Managed(u8).init(allocator);1613 var buf = std.array_list.Managed(u8).init(allocator);
...@@ -1784,7 +1783,7 @@ const ArgvToScriptCommandLineError = error{...@@ -1784,7 +1783,7 @@ const ArgvToScriptCommandLineError = error{
1784/// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise.1783/// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise.
1785/// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem.1784/// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem.
1786fn argvToScriptCommandLineWindows(1785fn argvToScriptCommandLineWindows(
1787 allocator: mem.Allocator,1786 allocator: Allocator,
1788 /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD.1787 /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD.
1789 /// The script must have been verified to exist at this path before calling this function.1788 /// The script must have been verified to exist at this path before calling this function.
1790 script_path: []const u16,1789 script_path: []const u16,
lib/std/start.zig+13-3
...@@ -110,7 +110,7 @@ fn main2() callconv(.c) c_int {...@@ -110,7 +110,7 @@ fn main2() callconv(.c) c_int {
110}110}
111111
112fn _start2() callconv(.withStackAlign(.c, 1)) noreturn {112fn _start2() callconv(.withStackAlign(.c, 1)) noreturn {
113 std.posix.exit(callMain());113 std.process.exit(callMain());
114}114}
115115
116fn spirvMain2() callconv(.kernel) void {116fn spirvMain2() callconv(.kernel) void {
...@@ -118,7 +118,7 @@ fn spirvMain2() callconv(.kernel) void {...@@ -118,7 +118,7 @@ fn spirvMain2() callconv(.kernel) void {
118}118}
119119
120fn wWinMainCRTStartup2() callconv(.c) noreturn {120fn wWinMainCRTStartup2() callconv(.c) noreturn {
121 std.posix.exit(callMain());121 std.process.exit(callMain());
122}122}
123123
124////////////////////////////////////////////////////////////////////////////////124////////////////////////////////////////////////////////////////////////////////
...@@ -627,7 +627,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {...@@ -627,7 +627,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
627 for (slice) |func| func();627 for (slice) |func| func();
628 }628 }
629629
630 std.posix.exit(callMainWithArgs(argc, argv, envp));630 std.process.exit(callMainWithArgs(argc, argv, envp));
631}631}
632632
633fn expandStackSize(phdrs: []elf.Phdr) void {633fn expandStackSize(phdrs: []elf.Phdr) void {
...@@ -669,6 +669,11 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {...@@ -669,6 +669,11 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
669 std.os.argv = argv[0..argc];669 std.os.argv = argv[0..argc];
670 std.os.environ = envp;670 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
672 std.debug.maybeEnableSegfaultHandler();677 std.debug.maybeEnableSegfaultHandler();
673678
674 return callMain();679 return callMain();
...@@ -691,6 +696,11 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal...@@ -691,6 +696,11 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal
691696
692fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {697fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {
693 std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)];698 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
694 return callMain();704 return callMain();
695}705}
696706
lib/std/std.zig+23-3
...@@ -108,14 +108,14 @@ pub const start = @import("start.zig");...@@ -108,14 +108,14 @@ pub const start = @import("start.zig");
108108
109const root = @import("root");109const 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.
112pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options else .{};112pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options else .{};
113113
114pub const Options = struct {114pub const Options = struct {
115 enable_segfault_handler: bool = debug.default_enable_segfault_handler,115 enable_segfault_handler: bool = debug.default_enable_segfault_handler,
116116
117 /// Function used to implement `std.fs.cwd` for WASI.117 /// Function used to implement `std.Io.Dir.cwd` for WASI.
118 wasiCwd: fn () os.wasi.fd_t = fs.defaultWasiCwd,118 wasiCwd: fn () os.wasi.fd_t = os.defaultWasiCwd,
119119
120 /// The current log level.120 /// The current log level.
121 log_level: log.Level = log.default_level,121 log_level: log.Level = log.default_level,
...@@ -129,6 +129,8 @@ pub const Options = struct {...@@ -129,6 +129,8 @@ pub const Options = struct {
129 args: anytype,129 args: anytype,
130 ) void = log.defaultLog,130 ) void = log.defaultLog,
131131
132 logTerminalMode: fn () Io.Terminal.Mode = log.defaultTerminalMode,
133
132 /// Overrides `std.heap.page_size_min`.134 /// Overrides `std.heap.page_size_min`.
133 page_size_min: ?usize = null,135 page_size_min: ?usize = null,
134 /// Overrides `std.heap.page_size_max`.136 /// Overrides `std.heap.page_size_max`.
...@@ -173,6 +175,24 @@ pub const Options = struct {...@@ -173,6 +175,24 @@ pub const Options = struct {
173 /// If this is `false`, then captured stack traces will always be empty, and attempts to write175 /// If this is `false`, then captured stack traces will always be empty, and attempts to write
174 /// stack traces will just print an error to the relevant `Io.Writer` and return.176 /// stack traces will just print an error to the relevant `Io.Writer` and return.
175 allow_stack_tracing: bool = !@import("builtin").strip_debug_info,177 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;
176};196};
177197
178// This forces the start.zig file to be imported, and the comptime logic inside that198// 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 @@...@@ -16,6 +16,7 @@
16//! pax reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_1316//! pax reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13
1717
18const std = @import("std");18const std = @import("std");
19const Io = std.Io;
19const assert = std.debug.assert;20const assert = std.debug.assert;
20const testing = std.testing;21const testing = std.testing;
2122
...@@ -302,7 +303,7 @@ pub const FileKind = enum {...@@ -302,7 +303,7 @@ pub const FileKind = enum {
302303
303/// Iterator over entries in the tar file represented by reader.304/// Iterator over entries in the tar file represented by reader.
304pub const Iterator = struct {305pub const Iterator = struct {
305 reader: *std.Io.Reader,306 reader: *Io.Reader,
306 diagnostics: ?*Diagnostics = null,307 diagnostics: ?*Diagnostics = null,
307308
308 // buffers for heeader and file attributes309 // buffers for heeader and file attributes
...@@ -328,7 +329,7 @@ pub const Iterator = struct {...@@ -328,7 +329,7 @@ pub const Iterator = struct {
328329
329 /// Iterates over files in tar archive.330 /// Iterates over files in tar archive.
330 /// `next` returns each file in tar archive.331 /// `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 {
332 return .{333 return .{
333 .reader = reader,334 .reader = reader,
334 .diagnostics = options.diagnostics,335 .diagnostics = options.diagnostics,
...@@ -473,7 +474,7 @@ pub const Iterator = struct {...@@ -473,7 +474,7 @@ pub const Iterator = struct {
473 return null;474 return null;
474 }475 }
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 {
477 try it.reader.streamExact64(w, file.size);478 try it.reader.streamExact64(w, file.size);
478 it.unread_file_bytes = 0;479 it.unread_file_bytes = 0;
479 }480 }
...@@ -499,14 +500,14 @@ const pax_max_size_attr_len = 64;...@@ -499,14 +500,14 @@ const pax_max_size_attr_len = 64;
499500
500pub const PaxIterator = struct {501pub const PaxIterator = struct {
501 size: usize, // cumulative size of all pax attributes502 size: usize, // cumulative size of all pax attributes
502 reader: *std.Io.Reader,503 reader: *Io.Reader,
503504
504 const Self = @This();505 const Self = @This();
505506
506 const Attribute = struct {507 const Attribute = struct {
507 kind: PaxAttributeKind,508 kind: PaxAttributeKind,
508 len: usize, // length of the attribute value509 len: usize, // length of the attribute value
509 reader: *std.Io.Reader, // reader positioned at value start510 reader: *Io.Reader, // reader positioned at value start
510511
511 // Copies pax attribute value into destination buffer.512 // Copies pax attribute value into destination buffer.
512 // Must be called with destination buffer of size at least Attribute.len.513 // Must be called with destination buffer of size at least Attribute.len.
...@@ -573,13 +574,13 @@ pub const PaxIterator = struct {...@@ -573,13 +574,13 @@ pub const PaxIterator = struct {
573 }574 }
574575
575 // Checks that each record ends with new line.576 // Checks that each record ends with new line.
576 fn validateAttributeEnding(reader: *std.Io.Reader) !void {577 fn validateAttributeEnding(reader: *Io.Reader) !void {
577 if (try reader.takeByte() != '\n') return error.PaxInvalidAttributeEnd;578 if (try reader.takeByte() != '\n') return error.PaxInvalidAttributeEnd;
578 }579 }
579};580};
580581
581/// Saves tar file content to the file systems.582/// 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 {
583 var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;584 var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
584 var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;585 var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
585 var file_contents_buffer: [1024]u8 = undefined;586 var file_contents_buffer: [1024]u8 = undefined;
...@@ -605,13 +606,13 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp...@@ -605,13 +606,13 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp
605 switch (file.kind) {606 switch (file.kind) {
606 .directory => {607 .directory => {
607 if (file_name.len > 0 and !options.exclude_empty_directories) {608 if (file_name.len > 0 and !options.exclude_empty_directories) {
608 try dir.makePath(file_name);609 try dir.createDirPath(io, file_name);
609 }610 }
610 },611 },
611 .file => {612 .file => {
612 if (createDirAndFile(dir, file_name, fileMode(file.mode, options))) |fs_file| {613 if (createDirAndFile(io, dir, file_name, filePermissions(file.mode, options))) |fs_file| {
613 defer fs_file.close();614 defer fs_file.close(io);
614 var file_writer = fs_file.writer(&file_contents_buffer);615 var file_writer = fs_file.writer(io, &file_contents_buffer);
615 try it.streamRemaining(file, &file_writer.interface);616 try it.streamRemaining(file, &file_writer.interface);
616 try file_writer.interface.flush();617 try file_writer.interface.flush();
617 } else |err| {618 } else |err| {
...@@ -624,7 +625,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp...@@ -624,7 +625,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp
624 },625 },
625 .sym_link => {626 .sym_link => {
626 const link_name = file.link_name;627 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| {
628 const d = options.diagnostics orelse return error.UnableToCreateSymLink;629 const d = options.diagnostics orelse return error.UnableToCreateSymLink;
629 try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{630 try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{
630 .code = err,631 .code = err,
...@@ -637,12 +638,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp...@@ -637,12 +638,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: *std.Io.Reader, options: PipeOp
637 }638 }
638}639}
639640
640fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mode) !std.fs.File {641fn createDirAndFile(io: Io, dir: Io.Dir, file_name: []const u8, permissions: Io.File.Permissions) !Io.File {
641 const fs_file = dir.createFile(file_name, .{ .exclusive = true, .mode = mode }) catch |err| {642 const fs_file = dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions }) catch |err| {
642 if (err == error.FileNotFound) {643 if (err == error.FileNotFound) {
643 if (std.fs.path.dirname(file_name)) |dir_name| {644 if (std.fs.path.dirname(file_name)) |dir_name| {
644 try dir.makePath(dir_name);645 try dir.createDirPath(io, dir_name);
645 return try dir.createFile(file_name, .{ .exclusive = true, .mode = mode });646 return try dir.createFile(io, file_name, .{ .exclusive = true, .permissions = permissions });
646 }647 }
647 }648 }
648 return err;649 return err;
...@@ -651,12 +652,12 @@ fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mo...@@ -651,12 +652,12 @@ fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8, mode: std.fs.File.Mo
651}652}
652653
653// Creates a symbolic link at path `file_name` which points to `link_name`.654// 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 {655fn createDirAndSymlink(io: Io, dir: Io.Dir, link_name: []const u8, file_name: []const u8) !void {
655 dir.symLink(link_name, file_name, .{}) catch |err| {656 dir.symLink(io, link_name, file_name, .{}) catch |err| {
656 if (err == error.FileNotFound) {657 if (err == error.FileNotFound) {
657 if (std.fs.path.dirname(file_name)) |dir_name| {658 if (std.fs.path.dirname(file_name)) |dir_name| {
658 try dir.makePath(dir_name);659 try dir.createDirPath(io, dir_name);
659 return try dir.symLink(link_name, file_name, .{});660 return try dir.symLink(io, link_name, file_name, .{});
660 }661 }
661 }662 }
662 return err;663 return err;
...@@ -783,7 +784,7 @@ test PaxIterator {...@@ -783,7 +784,7 @@ test PaxIterator {
783 var buffer: [1024]u8 = undefined;784 var buffer: [1024]u8 = undefined;
784785
785 outer: for (cases) |case| {786 outer: for (cases) |case| {
786 var reader: std.Io.Reader = .fixed(case.data);787 var reader: Io.Reader = .fixed(case.data);
787 var it: PaxIterator = .{788 var it: PaxIterator = .{
788 .size = case.data.len,789 .size = case.data.len,
789 .reader = &reader,790 .reader = &reader,
...@@ -874,25 +875,27 @@ test "header parse mode" {...@@ -874,25 +875,27 @@ test "header parse mode" {
874}875}
875876
876test "create file and symlink" {877test "create file and symlink" {
878 const io = testing.io;
879
877 var root = testing.tmpDir(.{});880 var root = testing.tmpDir(.{});
878 defer root.cleanup();881 defer root.cleanup();
879882
880 var file = try createDirAndFile(root.dir, "file1", default_mode);883 var file = try createDirAndFile(io, root.dir, "file1", .default_file);
881 file.close();884 file.close(io);
882 file = try createDirAndFile(root.dir, "a/b/c/file2", default_mode);885 file = try createDirAndFile(io, root.dir, "a/b/c/file2", .default_file);
883 file.close();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| {
886 // On Windows when developer mode is not enabled889 // On Windows when developer mode is not enabled
887 if (err == error.AccessDenied) return error.SkipZigTest;890 if (err == error.AccessDenied) return error.SkipZigTest;
888 return err;891 return err;
889 };892 };
890 try createDirAndSymlink(root.dir, "../../../file1", "d/e/f/symlink2");893 try createDirAndSymlink(io, root.dir, "../../../file1", "d/e/f/symlink2");
891894
892 // Danglink symlnik, file created later895 // Danglink symlnik, file created later
893 try createDirAndSymlink(root.dir, "../../../g/h/i/file4", "j/k/l/symlink3");896 try createDirAndSymlink(io, root.dir, "../../../g/h/i/file4", "j/k/l/symlink3");
894 file = try createDirAndFile(root.dir, "g/h/i/file4", default_mode);897 file = try createDirAndFile(io, root.dir, "g/h/i/file4", .default_file);
895 file.close();898 file.close(io);
896}899}
897900
898test Iterator {901test Iterator {
...@@ -916,7 +919,7 @@ test Iterator {...@@ -916,7 +919,7 @@ test Iterator {
916 // example/empty/919 // example/empty/
917920
918 const data = @embedFile("tar/testdata/example.tar");921 const data = @embedFile("tar/testdata/example.tar");
919 var reader: std.Io.Reader = .fixed(data);922 var reader: Io.Reader = .fixed(data);
920923
921 // User provided buffers to the iterator924 // User provided buffers to the iterator
922 var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;925 var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined;
...@@ -942,7 +945,7 @@ test Iterator {...@@ -942,7 +945,7 @@ test Iterator {
942 .file => {945 .file => {
943 try testing.expectEqualStrings("example/a/file", file.name);946 try testing.expectEqualStrings("example/a/file", file.name);
944 var buf: [16]u8 = undefined;947 var buf: [16]u8 = undefined;
945 var w: std.Io.Writer = .fixed(&buf);948 var w: Io.Writer = .fixed(&buf);
946 try it.streamRemaining(file, &w);949 try it.streamRemaining(file, &w);
947 try testing.expectEqualStrings("content\n", w.buffered());950 try testing.expectEqualStrings("content\n", w.buffered());
948 },951 },
...@@ -955,6 +958,7 @@ test Iterator {...@@ -955,6 +958,7 @@ test Iterator {
955}958}
956959
957test pipeToFileSystem {960test pipeToFileSystem {
961 const io = testing.io;
958 // Example tar file is created from this tree structure:962 // Example tar file is created from this tree structure:
959 // $ tree example963 // $ tree example
960 // example964 // example
...@@ -975,14 +979,14 @@ test pipeToFileSystem {...@@ -975,14 +979,14 @@ test pipeToFileSystem {
975 // example/empty/979 // example/empty/
976980
977 const data = @embedFile("tar/testdata/example.tar");981 const data = @embedFile("tar/testdata/example.tar");
978 var reader: std.Io.Reader = .fixed(data);982 var reader: Io.Reader = .fixed(data);
979983
980 var tmp = testing.tmpDir(.{ .follow_symlinks = false });984 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
981 defer tmp.cleanup();985 defer tmp.cleanup();
982 const dir = tmp.dir;986 const dir = tmp.dir;
983987
984 // Save tar from reader to the file system `dir`988 // Save tar from reader to the file system `dir`
985 pipeToFileSystem(dir, &reader, .{989 pipeToFileSystem(io, dir, &reader, .{
986 .mode_mode = .ignore,990 .mode_mode = .ignore,
987 .strip_components = 1,991 .strip_components = 1,
988 .exclude_empty_directories = true,992 .exclude_empty_directories = true,
...@@ -992,21 +996,22 @@ test pipeToFileSystem {...@@ -992,21 +996,22 @@ test pipeToFileSystem {
992 return err;996 return err;
993 };997 };
994998
995 try testing.expectError(error.FileNotFound, dir.statFile("empty"));999 try testing.expectError(error.FileNotFound, dir.statFile(io, "empty", .{}));
996 try testing.expect((try dir.statFile("a/file")).kind == .file);1000 try testing.expect((try dir.statFile(io, "a/file", .{})).kind == .file);
997 try testing.expect((try dir.statFile("b/symlink")).kind == .file); // statFile follows symlink1001 try testing.expect((try dir.statFile(io, "b/symlink", .{})).kind == .file); // statFile follows symlink
9981002
999 var buf: [32]u8 = undefined;1003 var buf: [32]u8 = undefined;
1000 try testing.expectEqualSlices(1004 try testing.expectEqualSlices(
1001 u8,1005 u8,
1002 "../a/file",1006 "../a/file",
1003 normalizePath(try dir.readLink("b/symlink", &buf)),1007 normalizePath(buf[0..try dir.readLink(io, "b/symlink", &buf)]),
1004 );1008 );
1005}1009}
10061010
1007test "pipeToFileSystem root_dir" {1011test "pipeToFileSystem root_dir" {
1012 const io = testing.io;
1008 const data = @embedFile("tar/testdata/example.tar");1013 const data = @embedFile("tar/testdata/example.tar");
1009 var reader: std.Io.Reader = .fixed(data);1014 var reader: Io.Reader = .fixed(data);
10101015
1011 // with strip_components = 11016 // with strip_components = 1
1012 {1017 {
...@@ -1015,7 +1020,7 @@ test "pipeToFileSystem root_dir" {...@@ -1015,7 +1020,7 @@ test "pipeToFileSystem root_dir" {
1015 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };1020 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
1016 defer diagnostics.deinit();1021 defer diagnostics.deinit();
10171022
1018 pipeToFileSystem(tmp.dir, &reader, .{1023 pipeToFileSystem(io, tmp.dir, &reader, .{
1019 .strip_components = 1,1024 .strip_components = 1,
1020 .diagnostics = &diagnostics,1025 .diagnostics = &diagnostics,
1021 }) catch |err| {1026 }) catch |err| {
...@@ -1037,7 +1042,7 @@ test "pipeToFileSystem root_dir" {...@@ -1037,7 +1042,7 @@ test "pipeToFileSystem root_dir" {
1037 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };1042 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
1038 defer diagnostics.deinit();1043 defer diagnostics.deinit();
10391044
1040 pipeToFileSystem(tmp.dir, &reader, .{1045 pipeToFileSystem(io, tmp.dir, &reader, .{
1041 .strip_components = 0,1046 .strip_components = 0,
1042 .diagnostics = &diagnostics,1047 .diagnostics = &diagnostics,
1043 }) catch |err| {1048 }) catch |err| {
...@@ -1053,43 +1058,46 @@ test "pipeToFileSystem root_dir" {...@@ -1053,43 +1058,46 @@ test "pipeToFileSystem root_dir" {
1053}1058}
10541059
1055test "findRoot with single file archive" {1060test "findRoot with single file archive" {
1061 const io = testing.io;
1056 const data = @embedFile("tar/testdata/22752.tar");1062 const data = @embedFile("tar/testdata/22752.tar");
1057 var reader: std.Io.Reader = .fixed(data);1063 var reader: Io.Reader = .fixed(data);
10581064
1059 var tmp = testing.tmpDir(.{});1065 var tmp = testing.tmpDir(.{});
1060 defer tmp.cleanup();1066 defer tmp.cleanup();
10611067
1062 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };1068 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
1063 defer diagnostics.deinit();1069 defer diagnostics.deinit();
1064 try pipeToFileSystem(tmp.dir, &reader, .{ .diagnostics = &diagnostics });1070 try pipeToFileSystem(io, tmp.dir, &reader, .{ .diagnostics = &diagnostics });
10651071
1066 try testing.expectEqualStrings("", diagnostics.root_dir);1072 try testing.expectEqualStrings("", diagnostics.root_dir);
1067}1073}
10681074
1069test "findRoot without explicit root dir" {1075test "findRoot without explicit root dir" {
1076 const io = testing.io;
1070 const data = @embedFile("tar/testdata/19820.tar");1077 const data = @embedFile("tar/testdata/19820.tar");
1071 var reader: std.Io.Reader = .fixed(data);1078 var reader: Io.Reader = .fixed(data);
10721079
1073 var tmp = testing.tmpDir(.{});1080 var tmp = testing.tmpDir(.{});
1074 defer tmp.cleanup();1081 defer tmp.cleanup();
10751082
1076 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };1083 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
1077 defer diagnostics.deinit();1084 defer diagnostics.deinit();
1078 try pipeToFileSystem(tmp.dir, &reader, .{ .diagnostics = &diagnostics });1085 try pipeToFileSystem(io, tmp.dir, &reader, .{ .diagnostics = &diagnostics });
10791086
1080 try testing.expectEqualStrings("root", diagnostics.root_dir);1087 try testing.expectEqualStrings("root", diagnostics.root_dir);
1081}1088}
10821089
1083test "pipeToFileSystem strip_components" {1090test "pipeToFileSystem strip_components" {
1091 const io = testing.io;
1084 const data = @embedFile("tar/testdata/example.tar");1092 const data = @embedFile("tar/testdata/example.tar");
1085 var reader: std.Io.Reader = .fixed(data);1093 var reader: Io.Reader = .fixed(data);
10861094
1087 var tmp = testing.tmpDir(.{ .follow_symlinks = false });1095 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
1088 defer tmp.cleanup();1096 defer tmp.cleanup();
1089 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };1097 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
1090 defer diagnostics.deinit();1098 defer diagnostics.deinit();
10911099
1092 pipeToFileSystem(tmp.dir, &reader, .{1100 pipeToFileSystem(io, tmp.dir, &reader, .{
1093 .strip_components = 3,1101 .strip_components = 3,
1094 .diagnostics = &diagnostics,1102 .diagnostics = &diagnostics,
1095 }) catch |err| {1103 }) catch |err| {
...@@ -1110,45 +1118,36 @@ fn normalizePath(bytes: []u8) []u8 {...@@ -1110,45 +1118,36 @@ fn normalizePath(bytes: []u8) []u8 {
1110 return bytes;1118 return bytes;
1111}1119}
11121120
1113const default_mode = std.fs.File.default_mode;
1114
1115// File system mode based on tar header mode and mode_mode options.1121// File system mode based on tar header mode and mode_mode options.
1116fn fileMode(mode: u32, options: PipeOptions) std.fs.File.Mode {1122fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions {
1117 if (!std.fs.has_executable_bit or options.mode_mode == .ignore)1123 return if (!Io.File.Permissions.has_executable_bit or options.mode_mode == .ignore or (mode & 0o100) == 0)
1118 return default_mode;1124 .default_file
11191125 else
1120 const S = std.posix.S;1126 .executable_file;
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;
1129}1127}
11301128
1131test fileMode {1129test filePermissions {
1132 if (!std.fs.has_executable_bit) return error.SkipZigTest;1130 if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest;
1133 try testing.expectEqual(default_mode, fileMode(0o744, PipeOptions{ .mode_mode = .ignore }));1131 try testing.expectEqual(.default_file, filePermissions(0o744, .{ .mode_mode = .ignore }));
1134 try testing.expectEqual(0o777, fileMode(0o744, PipeOptions{}));1132 try testing.expectEqual(.executable_file, filePermissions(0o744, .{}));
1135 try testing.expectEqual(0o666, fileMode(0o644, PipeOptions{}));1133 try testing.expectEqual(.default_file, filePermissions(0o644, .{}));
1136 try testing.expectEqual(0o666, fileMode(0o655, PipeOptions{}));1134 try testing.expectEqual(.default_file, filePermissions(0o655, .{}));
1137}1135}
11381136
1139test "executable bit" {1137test "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;
1142 const S = std.posix.S;1141 const S = std.posix.S;
1143 const data = @embedFile("tar/testdata/example.tar");1142 const data = @embedFile("tar/testdata/example.tar");
11441143
1145 for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| {1144 for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| {
1146 var reader: std.Io.Reader = .fixed(data);1145 var reader: Io.Reader = .fixed(data);
11471146
1148 var tmp = testing.tmpDir(.{ .follow_symlinks = false });1147 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
1149 //defer tmp.cleanup();1148 //defer tmp.cleanup();
11501149
1151 pipeToFileSystem(tmp.dir, &reader, .{1150 pipeToFileSystem(io, tmp.dir, &reader, .{
1152 .strip_components = 1,1151 .strip_components = 1,
1153 .exclude_empty_directories = true,1152 .exclude_empty_directories = true,
1154 .mode_mode = opt,1153 .mode_mode = opt,
...@@ -1158,19 +1157,21 @@ test "executable bit" {...@@ -1158,19 +1157,21 @@ test "executable bit" {
1158 return err;1157 return err;
1159 };1158 };
11601159
1161 const fs = try tmp.dir.statFile("a/file");1160 const fs = try tmp.dir.statFile(io, "a/file", .{});
1162 try testing.expect(fs.kind == .file);1161 try testing.expect(fs.kind == .file);
11631162
1163 const mode = fs.permissions.toMode();
1164
1164 if (opt == .executable_bit_only) {1165 if (opt == .executable_bit_only) {
1165 // Executable bit is set for user, group and others1166 // Executable bit is set for user, group and others
1166 try testing.expect(fs.mode & S.IXUSR > 0);1167 try testing.expect(mode & S.IXUSR > 0);
1167 try testing.expect(fs.mode & S.IXGRP > 0);1168 try testing.expect(mode & S.IXGRP > 0);
1168 try testing.expect(fs.mode & S.IXOTH > 0);1169 try testing.expect(mode & S.IXOTH > 0);
1169 }1170 }
1170 if (opt == .ignore) {1171 if (opt == .ignore) {
1171 try testing.expect(fs.mode & S.IXUSR == 0);1172 try testing.expect(mode & S.IXUSR == 0);
1172 try testing.expect(fs.mode & S.IXGRP == 0);1173 try testing.expect(mode & S.IXGRP == 0);
1173 try testing.expect(fs.mode & S.IXOTH == 0);1174 try testing.expect(mode & S.IXOTH == 0);
1174 }1175 }
1175 }1176 }
1176}1177}
lib/std/tar/test.zig+7-5
...@@ -424,6 +424,7 @@ test "insufficient buffer in Header name filed" {...@@ -424,6 +424,7 @@ test "insufficient buffer in Header name filed" {
424}424}
425425
426test "should not overwrite existing file" {426test "should not overwrite existing file" {
427 const io = testing.io;
427 // Starting from this folder structure:428 // Starting from this folder structure:
428 // $ tree root429 // $ tree root
429 // root430 // root
...@@ -469,17 +470,18 @@ test "should not overwrite existing file" {...@@ -469,17 +470,18 @@ test "should not overwrite existing file" {
469 defer root.cleanup();470 defer root.cleanup();
470 try testing.expectError(471 try testing.expectError(
471 error.PathAlreadyExists,472 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 }),
473 );474 );
474475
475 // Unpack with strip_components = 0 should pass476 // Unpack with strip_components = 0 should pass
476 r = .fixed(data);477 r = .fixed(data);
477 var root2 = std.testing.tmpDir(.{});478 var root2 = std.testing.tmpDir(.{});
478 defer root2.cleanup();479 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 });
480}481}
481482
482test "case sensitivity" {483test "case sensitivity" {
484 const io = testing.io;
483 // Mimicking issue #18089, this tar contains, same file name in two case485 // Mimicking issue #18089, this tar contains, same file name in two case
484 // sensitive name version. Should fail on case insensitive file systems.486 // sensitive name version. Should fail on case insensitive file systems.
485 //487 //
...@@ -495,13 +497,13 @@ test "case sensitivity" {...@@ -495,13 +497,13 @@ test "case sensitivity" {
495 var root = std.testing.tmpDir(.{});497 var root = std.testing.tmpDir(.{});
496 defer root.cleanup();498 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| {
499 // on case insensitive fs we fail on overwrite existing file501 // on case insensitive fs we fail on overwrite existing file
500 try testing.expectEqual(error.PathAlreadyExists, err);502 try testing.expectEqual(error.PathAlreadyExists, err);
501 return;503 return;
502 };504 };
503505
504 // on case sensitive os both files are created506 // on case sensitive os both files are created
505 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);
506 try testing.expect((try root.dir.statFile("alacritty/Darkermatrix.yml")).kind == .file);508 try testing.expect((try root.dir.statFile(io, "alacritty/Darkermatrix.yml", .{})).kind == .file);
507}509}
lib/std/testing.zig+64-56
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1const std = @import("std.zig");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2
3const std = @import("std.zig");
4const Io = std.Io;
3const assert = std.debug.assert;5const assert = std.debug.assert;
4const math = std.math;6const math = std.math;
57
...@@ -28,8 +30,8 @@ pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{...@@ -28,8 +30,8 @@ pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{
28 break :b .init;30 break :b .init;
29};31};
3032
31pub var io_instance: std.Io.Threaded = undefined;33pub var io_instance: Io.Threaded = undefined;
32pub const io = io_instance.io();34pub const io = if (builtin.is_test) io_instance.io() else @compileError("not testing");
3335
34/// TODO https://github.com/ziglang/zig/issues/573836/// TODO https://github.com/ziglang/zig/issues/5738
35pub var log_level = std.log.Level.warn;37pub var log_level = std.log.Level.warn;
...@@ -352,11 +354,10 @@ test expectApproxEqRel {...@@ -352,11 +354,10 @@ test expectApproxEqRel {
352 }354 }
353}355}
354356
355/// This function is intended to be used only in tests. When the two slices are not357/// This function is intended to be used only in tests. When the two slices are
356/// equal, prints diagnostics to stderr to show exactly how they are not equal (with358/// not equal, prints diagnostics to stderr to show exactly how they are not
357/// the differences highlighted in red), then returns a test failure error.359/// equal (with the differences highlighted in red), then returns a test
358/// The colorized output is optional and controlled by the return of `std.Io.tty.Config.detect`.360/// failure error.
359/// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead.
360pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void {361pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void {
361 const diff_index: usize = diff_index: {362 const diff_index: usize = diff_index: {
362 const shortest = @min(expected.len, actual.len);363 const shortest = @min(expected.len, actual.len);
...@@ -367,9 +368,11 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const...@@ -367,9 +368,11 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const
367 break :diff_index if (expected.len == actual.len) return else shortest;368 break :diff_index if (expected.len == actual.len) return else shortest;
368 };369 };
369 if (!backend_can_print) return error.TestExpectedEqual;370 if (!backend_can_print) return error.TestExpectedEqual;
370 const stderr_w, const ttyconf = std.debug.lockStderrWriter(&.{});371 // Intentionally using the debug Io instance rather than the testing Io instance.
371 defer std.debug.unlockStderrWriter();372 const stderr = std.debug.lockStderr(&.{});
372 failEqualSlices(T, expected, actual, diff_index, stderr_w, ttyconf) catch {};373 defer std.debug.unlockStderr();
374 const w = &stderr.file_writer.interface;
375 failEqualSlices(T, expected, actual, diff_index, w, stderr.terminal_mode) catch {};
373 return error.TestExpectedEqual;376 return error.TestExpectedEqual;
374}377}
375378
...@@ -378,8 +381,8 @@ fn failEqualSlices(...@@ -378,8 +381,8 @@ fn failEqualSlices(
378 expected: []const T,381 expected: []const T,
379 actual: []const T,382 actual: []const T,
380 diff_index: usize,383 diff_index: usize,
381 w: *std.Io.Writer,384 w: *Io.Writer,
382 ttyconf: std.Io.tty.Config,385 terminal_mode: Io.Terminal.Mode,
383) !void {386) !void {
384 try w.print("slices differ. first difference occurs at index {d} (0x{X})\n", .{ diff_index, diff_index });387 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(...@@ -402,12 +405,12 @@ fn failEqualSlices(
402 var differ = if (T == u8) BytesDiffer{405 var differ = if (T == u8) BytesDiffer{
403 .expected = expected_window,406 .expected = expected_window,
404 .actual = actual_window,407 .actual = actual_window,
405 .ttyconf = ttyconf,408 .terminal_mode = terminal_mode,
406 } else SliceDiffer(T){409 } else SliceDiffer(T){
407 .start_index = window_start,410 .start_index = window_start,
408 .expected = expected_window,411 .expected = expected_window,
409 .actual = actual_window,412 .actual = actual_window,
410 .ttyconf = ttyconf,413 .terminal_mode = terminal_mode,
411 };414 };
412415
413 // Print indexes as hex for slices of u8 since it's more likely to be binary data where416 // 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 {...@@ -464,21 +467,22 @@ fn SliceDiffer(comptime T: type) type {
464 start_index: usize,467 start_index: usize,
465 expected: []const T,468 expected: []const T,
466 actual: []const T,469 actual: []const T,
467 ttyconf: std.Io.tty.Config,470 terminal_mode: Io.Terminal.Mode,
468471
469 const Self = @This();472 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 };
472 for (self.expected, 0..) |value, i| {476 for (self.expected, 0..) |value, i| {
473 const full_index = self.start_index + i;477 const full_index = self.start_index + i;
474 const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true;478 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);
476 if (@typeInfo(T) == .pointer) {480 if (@typeInfo(T) == .pointer) {
477 try writer.print("[{}]{*}: {any}\n", .{ full_index, value, value });481 try writer.print("[{}]{*}: {any}\n", .{ full_index, value, value });
478 } else {482 } else {
479 try writer.print("[{}]: {any}\n", .{ full_index, value });483 try writer.print("[{}]: {any}\n", .{ full_index, value });
480 }484 }
481 if (diff) try self.ttyconf.setColor(writer, .reset);485 if (diff) try t.setColor(.reset);
482 }486 }
483 }487 }
484 };488 };
...@@ -487,9 +491,9 @@ fn SliceDiffer(comptime T: type) type {...@@ -487,9 +491,9 @@ fn SliceDiffer(comptime T: type) type {
487const BytesDiffer = struct {491const BytesDiffer = struct {
488 expected: []const u8,492 expected: []const u8,
489 actual: []const u8,493 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 {
493 var expected_iterator = std.mem.window(u8, self.expected, 16, 16);497 var expected_iterator = std.mem.window(u8, self.expected, 16, 16);
494 var row: usize = 0;498 var row: usize = 0;
495 while (expected_iterator.next()) |chunk| {499 while (expected_iterator.next()) |chunk| {
...@@ -514,7 +518,7 @@ const BytesDiffer = struct {...@@ -514,7 +518,7 @@ const BytesDiffer = struct {
514 try self.writeDiff(writer, "{c}", .{byte}, diff);518 try self.writeDiff(writer, "{c}", .{byte}, diff);
515 } else {519 } else {
516 // TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed520 // 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) {
518 try self.writeDiff(writer, ".", .{}, diff);522 try self.writeDiff(writer, ".", .{}, diff);
519 continue;523 continue;
520 }524 }
...@@ -535,10 +539,14 @@ const BytesDiffer = struct {...@@ -535,10 +539,14 @@ const BytesDiffer = struct {
535 }539 }
536 }540 }
537541
538 fn writeDiff(self: BytesDiffer, writer: *std.Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void {542 fn terminal(self: *const BytesDiffer, writer: *Io.Writer) Io.Terminal {
539 if (diff) try self.ttyconf.setColor(writer, .red);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);
540 try writer.print(fmt, args);548 try writer.print(fmt, args);
541 if (diff) try self.ttyconf.setColor(writer, .reset);549 if (diff) try self.terminal(writer).setColor(.reset);
542 }550 }
543};551};
544552
...@@ -605,34 +613,35 @@ pub fn expect(ok: bool) !void {...@@ -605,34 +613,35 @@ pub fn expect(ok: bool) !void {
605}613}
606614
607pub const TmpDir = struct {615pub const TmpDir = struct {
608 dir: std.fs.Dir,616 dir: Io.Dir,
609 parent_dir: std.fs.Dir,617 parent_dir: Io.Dir,
610 sub_path: [sub_path_len]u8,618 sub_path: [sub_path_len]u8,
611619
612 const random_bytes_count = 12;620 const random_bytes_count = 12;
613 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);621 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
614622
615 pub fn cleanup(self: *TmpDir) void {623 pub fn cleanup(self: *TmpDir) void {
616 self.dir.close();624 self.dir.close(io);
617 self.parent_dir.deleteTree(&self.sub_path) catch {};625 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
618 self.parent_dir.close();626 self.parent_dir.close(io);
619 self.* = undefined;627 self.* = undefined;
620 }628 }
621};629};
622630
623pub fn tmpDir(opts: std.fs.Dir.OpenOptions) TmpDir {631pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir {
632 comptime assert(builtin.is_test);
624 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;633 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
625 std.crypto.random.bytes(&random_bytes);634 std.crypto.random.bytes(&random_bytes);
626 var sub_path: [TmpDir.sub_path_len]u8 = undefined;635 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
627 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);636 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
628637
629 const cwd = std.fs.cwd();638 const cwd = Io.Dir.cwd();
630 var cache_dir = cwd.makeOpenPath(".zig-cache", .{}) catch639 var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch
631 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");640 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");
632 defer cache_dir.close();641 defer cache_dir.close(io);
633 const parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch642 const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch
634 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir");643 @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) catch644 const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch
636 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");645 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
637646
638 return .{647 return .{
...@@ -929,7 +938,7 @@ test "expectEqualDeep primitive type" {...@@ -929,7 +938,7 @@ test "expectEqualDeep primitive type" {
929 a,938 a,
930 b,939 b,
931940
932 pub fn format(self: @This(), writer: *std.Io.Writer) !void {941 pub fn format(self: @This(), writer: *Io.Writer) !void {
933 try writer.writeAll(@tagName(self));942 try writer.writeAll(@tagName(self));
934 }943 }
935 };944 };
...@@ -1146,9 +1155,10 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime...@@ -1146,9 +1155,10 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
1146 break :x failing_allocator_inst.alloc_index;1155 break :x failing_allocator_inst.alloc_index;
1147 };1156 };
11481157
1149 var fail_index: usize = 0;1158 for (0..needed_alloc_count) |fail_index| {
1150 while (fail_index < needed_alloc_count) : (fail_index += 1) {1159 var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{
1151 var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ .fail_index = fail_index });1160 .fail_index = fail_index,
1161 });
1152 args.@"0" = failing_allocator_inst.allocator();1162 args.@"0" = failing_allocator_inst.allocator();
11531163
1154 if (@call(.auto, test_fn, args)) |_| {1164 if (@call(.auto, test_fn, args)) |_| {
...@@ -1160,7 +1170,6 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime...@@ -1160,7 +1170,6 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
1160 } else |err| switch (err) {1170 } else |err| switch (err) {
1161 error.OutOfMemory => {1171 error.OutOfMemory => {
1162 if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {1172 if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {
1163 const tty_config: std.Io.tty.Config = .detect(.stderr());
1164 print(1173 print(
1165 "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}",1174 "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}",
1166 .{1175 .{
...@@ -1172,7 +1181,6 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime...@@ -1172,7 +1181,6 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
1172 failing_allocator_inst.deallocations,1181 failing_allocator_inst.deallocations,
1173 std.debug.FormatStackTrace{1182 std.debug.FormatStackTrace{
1174 .stack_trace = failing_allocator_inst.getStackTrace(),1183 .stack_trace = failing_allocator_inst.getStackTrace(),
1175 .tty_config = tty_config,
1176 },1184 },
1177 },1185 },
1178 );1186 );
...@@ -1220,14 +1228,14 @@ pub inline fn fuzz(...@@ -1220,14 +1228,14 @@ pub inline fn fuzz(
1220 return @import("root").fuzz(context, testOne, options);1228 return @import("root").fuzz(context, testOne, options);
1221}1229}
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`.
1224pub const Reader = struct {1232pub const Reader = struct {
1225 calls: []const Call,1233 calls: []const Call,
1226 interface: std.Io.Reader,1234 interface: Io.Reader,
1227 next_call_index: usize,1235 next_call_index: usize,
1228 next_offset: usize,1236 next_offset: usize,
1229 /// Further reduces how many bytes are written in each `stream` call.1237 /// 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
1232 pub const Call = struct {1240 pub const Call = struct {
1233 buffer: []const u8,1241 buffer: []const u8,
...@@ -1247,7 +1255,7 @@ pub const Reader = struct {...@@ -1247,7 +1255,7 @@ pub const Reader = struct {
1247 };1255 };
1248 }1256 }
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 {
1251 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));1259 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
1252 if (r.calls.len - r.next_call_index == 0) return error.EndOfStream;1260 if (r.calls.len - r.next_call_index == 0) return error.EndOfStream;
1253 const call = r.calls[r.next_call_index];1261 const call = r.calls[r.next_call_index];
...@@ -1262,13 +1270,13 @@ pub const Reader = struct {...@@ -1262,13 +1270,13 @@ pub const Reader = struct {
1262 }1270 }
1263};1271};
12641272
1265/// A `std.Io.Reader` that gets its data from another `std.Io.Reader`, and always1273/// A `Io.Reader` that gets its data from another `Io.Reader`, and always
1266/// writes to its own buffer (and returns 0) during `stream` and `readVec`.1274/// writes to its own buffer (and returns 0) during `stream` and `readVec`.
1267pub const ReaderIndirect = struct {1275pub const ReaderIndirect = struct {
1268 in: *std.Io.Reader,1276 in: *Io.Reader,
1269 interface: std.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 {
1272 return .{1280 return .{
1273 .in = in,1281 .in = in,
1274 .interface = .{1282 .interface = .{
...@@ -1283,17 +1291,17 @@ pub const ReaderIndirect = struct {...@@ -1283,17 +1291,17 @@ pub const ReaderIndirect = struct {
1283 };1291 };
1284 }1292 }
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 {
1287 try streamInner(r);1295 try streamInner(r);
1288 return 0;1296 return 0;
1289 }1297 }
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 {
1292 try streamInner(r);1300 try streamInner(r);
1293 return 0;1301 return 0;
1294 }1302 }
12951303
1296 fn streamInner(r: *std.Io.Reader) std.Io.Reader.Error!void {1304 fn streamInner(r: *Io.Reader) Io.Reader.Error!void {
1297 const r_indirect: *ReaderIndirect = @alignCast(@fieldParentPtr("interface", r));1305 const r_indirect: *ReaderIndirect = @alignCast(@fieldParentPtr("interface", r));
12981306
1299 // If there's no room remaining in the buffer at all, make room.1307 // If there's no room remaining in the buffer at all, make room.
...@@ -1301,12 +1309,12 @@ pub const ReaderIndirect = struct {...@@ -1301,12 +1309,12 @@ pub const ReaderIndirect = struct {
1301 try r.rebase(r.buffer.len);1309 try r.rebase(r.buffer.len);
1302 }1310 }
13031311
1304 var writer: std.Io.Writer = .{1312 var writer: Io.Writer = .{
1305 .buffer = r.buffer,1313 .buffer = r.buffer,
1306 .end = r.end,1314 .end = r.end,
1307 .vtable = &.{1315 .vtable = &.{
1308 .drain = std.Io.Writer.unreachableDrain,1316 .drain = Io.Writer.unreachableDrain,
1309 .rebase = std.Io.Writer.unreachableRebase,1317 .rebase = Io.Writer.unreachableRebase,
1310 },1318 },
1311 };1319 };
1312 defer r.end = writer.end;1320 defer r.end = writer.end;
lib/std/unicode/throughput_test.zig+3-3
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const time = std.time;3const time = std.time;
3const unicode = std.unicode;4const unicode = std.unicode;
45const Timer = std.time.Timer;
5const Timer = time.Timer;
66
7const N = 1_000_000;7const N = 1_000_000;
88
...@@ -41,7 +41,7 @@ fn benchmarkCodepointCount(buf: []const u8) !ResultCount {...@@ -41,7 +41,7 @@ fn benchmarkCodepointCount(buf: []const u8) !ResultCount {
41pub fn main() !void {41pub fn main() !void {
42 // Size of buffer is about size of printed message.42 // Size of buffer is about size of printed message.
43 var stdout_buffer: [0x100]u8 = undefined;43 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);
45 const stdout = &stdout_writer.interface;45 const stdout = &stdout_writer.interface;
4646
47 try stdout.print("short ASCII strings\n", .{});47 try stdout.print("short ASCII strings\n", .{});
lib/std/zig.zig+7-14
...@@ -46,23 +46,16 @@ pub const SrcHasher = std.crypto.hash.Blake3;...@@ -46,23 +46,16 @@ pub const SrcHasher = std.crypto.hash.Blake3;
46pub const SrcHash = [16]u8;46pub const SrcHash = [16]u8;
4747
48pub const Color = enum {48pub const Color = enum {
49 /// Determine whether stderr is a terminal or not automatically.49 /// Auto-detect whether stream supports terminal colors.
50 auto,50 auto,
51 /// Assume stderr is not a terminal.51 /// Force-enable colors.
52 off,52 off,
53 /// Assume stderr is a terminal.53 /// Suppress colors.
54 on,54 on,
5555
56 pub fn getTtyConf(color: Color, detected: Io.tty.Config) Io.tty.Config {56 pub fn terminalMode(color: Color) ?Io.Terminal.Mode {
57 return switch (color) {57 return switch (color) {
58 .auto => detected,58 .auto => null,
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()),
66 .on => .escape_codes,59 .on => .escape_codes,
67 .off => .no_color,60 .off => .no_color,
68 };61 };
...@@ -639,7 +632,7 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![...@@ -639,7 +632,7 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![
639 return buffer.toOwnedSliceSentinel(gpa, 0);632 return buffer.toOwnedSliceSentinel(gpa, 0);
640}633}
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 {
643 var wip_errors: std.zig.ErrorBundle.Wip = undefined;636 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
644 try wip_errors.init(gpa);637 try wip_errors.init(gpa);
645 defer wip_errors.deinit();638 defer wip_errors.deinit();
...@@ -648,7 +641,7 @@ pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color...@@ -648,7 +641,7 @@ pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color
648641
649 var error_bundle = try wip_errors.toOwnedBundle("");642 var error_bundle = try wip_errors.toOwnedBundle("");
650 defer error_bundle.deinit(gpa);643 defer error_bundle.deinit(gpa);
651 error_bundle.renderToStdErr(.{}, color);644 return error_bundle.renderToStderr(io, .{}, color);
652}645}
653646
654pub fn putAstErrorsIntoBundle(647pub fn putAstErrorsIntoBundle(
lib/std/zig/ErrorBundle.zig+44-34
...@@ -162,45 +162,57 @@ pub const RenderOptions = struct {...@@ -162,45 +162,57 @@ pub const RenderOptions = struct {
162 include_log_text: bool = true,162 include_log_text: bool = true,
163};163};
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 {
166 var buffer: [256]u8 = undefined;168 var buffer: [256]u8 = undefined;
167 const w, const ttyconf = std.debug.lockStderrWriter(&buffer);169 const stderr = try io.lockStderr(&buffer, color.terminalMode());
168 defer std.debug.unlockStderrWriter();170 defer io.unlockStderr();
169 renderToWriter(eb, options, w, color.getTtyConf(ttyconf)) catch return;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 };
170}182}
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 {
173 if (eb.extra.len == 0) return;185 if (eb.extra.len == 0) return;
174 for (eb.getMessages()) |err_msg| {186 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);
176 }188 }
177189
178 if (options.include_log_text) {190 if (options.include_log_text) {
179 const log_text = eb.getCompileLogOutput();191 const log_text = eb.getCompileLogOutput();
180 if (log_text.len != 0) {192 if (log_text.len != 0) {
181 try w.writeAll("\nCompile Log Output:\n");193 try t.writer.writeAll("\nCompile Log Output:\n");
182 try w.writeAll(log_text);194 try t.writer.writeAll(log_text);
183 }195 }
184 }196 }
185}197}
186198
187fn renderErrorMessageToWriter(199fn renderErrorMessage(
188 eb: ErrorBundle,200 eb: ErrorBundle,
189 options: RenderOptions,201 options: RenderOptions,
190 err_msg_index: MessageIndex,202 err_msg_index: MessageIndex,
191 w: *Writer,203 t: Io.Terminal,
192 ttyconf: Io.tty.Config,
193 kind: []const u8,204 kind: []const u8,
194 color: Io.tty.Color,205 color: Io.Terminal.Color,
195 indent: usize,206 indent: usize,
196) (Writer.Error || std.posix.UnexpectedError)!void {207) Io.Terminal.SetColorError!void {
208 const w = t.writer;
197 const err_msg = eb.getErrorMessage(err_msg_index);209 const err_msg = eb.getErrorMessage(err_msg_index);
198 if (err_msg.src_loc != .none) {210 if (err_msg.src_loc != .none) {
199 const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc));211 const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc));
200 var prefix: Writer.Discarding = .init(&.{});212 var prefix: Writer.Discarding = .init(&.{});
201 try w.splatByteAll(' ', indent);213 try w.splatByteAll(' ', indent);
202 prefix.count += indent;214 prefix.count += indent;
203 try ttyconf.setColor(w, .bold);215 try t.setColor(.bold);
204 try w.print("{s}:{d}:{d}: ", .{216 try w.print("{s}:{d}:{d}: ", .{
205 eb.nullTerminatedString(src.data.src_path),217 eb.nullTerminatedString(src.data.src_path),
206 src.data.line + 1,218 src.data.line + 1,
...@@ -211,7 +223,7 @@ fn renderErrorMessageToWriter(...@@ -211,7 +223,7 @@ fn renderErrorMessageToWriter(
211 src.data.line + 1,223 src.data.line + 1,
212 src.data.column + 1,224 src.data.column + 1,
213 });225 });
214 try ttyconf.setColor(w, color);226 try t.setColor(color);
215 try w.writeAll(kind);227 try w.writeAll(kind);
216 prefix.count += kind.len;228 prefix.count += kind.len;
217 try w.writeAll(": ");229 try w.writeAll(": ");
...@@ -219,17 +231,17 @@ fn renderErrorMessageToWriter(...@@ -219,17 +231,17 @@ fn renderErrorMessageToWriter(
219 // This is the length of the part before the error message:231 // This is the length of the part before the error message:
220 // e.g. "file.zig:4:5: error: "232 // e.g. "file.zig:4:5: error: "
221 const prefix_len: usize = @intCast(prefix.count);233 const prefix_len: usize = @intCast(prefix.count);
222 try ttyconf.setColor(w, .reset);234 try t.setColor(.reset);
223 try ttyconf.setColor(w, .bold);235 try t.setColor(.bold);
224 if (err_msg.count == 1) {236 if (err_msg.count == 1) {
225 try writeMsg(eb, err_msg, w, prefix_len);237 try writeMsg(eb, err_msg, w, prefix_len);
226 try w.writeByte('\n');238 try w.writeByte('\n');
227 } else {239 } else {
228 try writeMsg(eb, err_msg, w, prefix_len);240 try writeMsg(eb, err_msg, w, prefix_len);
229 try ttyconf.setColor(w, .dim);241 try t.setColor(.dim);
230 try w.print(" ({d} times)\n", .{err_msg.count});242 try w.print(" ({d} times)\n", .{err_msg.count});
231 }243 }
232 try ttyconf.setColor(w, .reset);244 try t.setColor(.reset);
233 if (src.data.source_line != 0 and options.include_source_line) {245 if (src.data.source_line != 0 and options.include_source_line) {
234 const line = eb.nullTerminatedString(src.data.source_line);246 const line = eb.nullTerminatedString(src.data.source_line);
235 for (line) |b| switch (b) {247 for (line) |b| switch (b) {
...@@ -242,19 +254,19 @@ fn renderErrorMessageToWriter(...@@ -242,19 +254,19 @@ fn renderErrorMessageToWriter(
242 // -1 since span.main includes the caret254 // -1 since span.main includes the caret
243 const after_caret = src.data.span_end -| src.data.span_main -| 1;255 const after_caret = src.data.span_end -| src.data.span_main -| 1;
244 try w.splatByteAll(' ', src.data.column - before_caret);256 try w.splatByteAll(' ', src.data.column - before_caret);
245 try ttyconf.setColor(w, .green);257 try t.setColor(.green);
246 try w.splatByteAll('~', before_caret);258 try w.splatByteAll('~', before_caret);
247 try w.writeByte('^');259 try w.writeByte('^');
248 try w.splatByteAll('~', after_caret);260 try w.splatByteAll('~', after_caret);
249 try w.writeByte('\n');261 try w.writeByte('\n');
250 try ttyconf.setColor(w, .reset);262 try t.setColor(.reset);
251 }263 }
252 for (eb.getNotes(err_msg_index)) |note| {264 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);
254 }266 }
255 if (src.data.reference_trace_len > 0 and options.include_reference_trace) {267 if (src.data.reference_trace_len > 0 and options.include_reference_trace) {
256 try ttyconf.setColor(w, .reset);268 try t.setColor(.reset);
257 try ttyconf.setColor(w, .dim);269 try t.setColor(.dim);
258 try w.print("referenced by:\n", .{});270 try w.print("referenced by:\n", .{});
259 var ref_index = src.end;271 var ref_index = src.end;
260 for (0..src.data.reference_trace_len) |_| {272 for (0..src.data.reference_trace_len) |_| {
...@@ -281,25 +293,25 @@ fn renderErrorMessageToWriter(...@@ -281,25 +293,25 @@ fn renderErrorMessageToWriter(
281 );293 );
282 }294 }
283 }295 }
284 try ttyconf.setColor(w, .reset);296 try t.setColor(.reset);
285 }297 }
286 } else {298 } else {
287 try ttyconf.setColor(w, color);299 try t.setColor(color);
288 try w.splatByteAll(' ', indent);300 try w.splatByteAll(' ', indent);
289 try w.writeAll(kind);301 try w.writeAll(kind);
290 try w.writeAll(": ");302 try w.writeAll(": ");
291 try ttyconf.setColor(w, .reset);303 try t.setColor(.reset);
292 const msg = eb.nullTerminatedString(err_msg.msg);304 const msg = eb.nullTerminatedString(err_msg.msg);
293 if (err_msg.count == 1) {305 if (err_msg.count == 1) {
294 try w.print("{s}\n", .{msg});306 try w.print("{s}\n", .{msg});
295 } else {307 } else {
296 try w.print("{s}", .{msg});308 try w.print("{s}", .{msg});
297 try ttyconf.setColor(w, .dim);309 try t.setColor(.dim);
298 try w.print(" ({d} times)\n", .{err_msg.count});310 try w.print(" ({d} times)\n", .{err_msg.count});
299 }311 }
300 try ttyconf.setColor(w, .reset);312 try t.setColor(.reset);
301 for (eb.getNotes(err_msg_index)) |note| {313 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);
303 }315 }
304 }316 }
305}317}
...@@ -806,12 +818,10 @@ pub const Wip = struct {...@@ -806,12 +818,10 @@ pub const Wip = struct {
806 };818 };
807 defer bundle.deinit(std.testing.allocator);819 defer bundle.deinit(std.testing.allocator);
808820
809 const ttyconf: Io.tty.Config = .no_color;
810
811 var bundle_buf: Writer.Allocating = .init(std.testing.allocator);821 var bundle_buf: Writer.Allocating = .init(std.testing.allocator);
812 const bundle_bw = &bundle_buf.interface;822 const bundle_bw = &bundle_buf.interface;
813 defer bundle_buf.deinit();823 defer bundle_buf.deinit();
814 try bundle.renderToWriter(.{ .ttyconf = ttyconf }, bundle_bw);824 try bundle.renderToWriter(bundle_bw);
815825
816 var copy = copy: {826 var copy = copy: {
817 var wip: ErrorBundle.Wip = undefined;827 var wip: ErrorBundle.Wip = undefined;
...@@ -827,7 +837,7 @@ pub const Wip = struct {...@@ -827,7 +837,7 @@ pub const Wip = struct {
827 var copy_buf: Writer.Allocating = .init(std.testing.allocator);837 var copy_buf: Writer.Allocating = .init(std.testing.allocator);
828 const copy_bw = &copy_buf.interface;838 const copy_bw = &copy_buf.interface;
829 defer copy_buf.deinit();839 defer copy_buf.deinit();
830 try copy.renderToWriter(.{ .ttyconf = ttyconf }, copy_bw);840 try copy.renderToWriter(copy_bw);
831841
832 try std.testing.expectEqualStrings(bundle_bw.written(), copy_bw.written());842 try std.testing.expectEqualStrings(bundle_bw.written(), copy_bw.written());
833 }843 }
lib/std/zig/LibCDirs.zig+11-8
...@@ -1,3 +1,11 @@...@@ -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
1libc_include_dir_list: []const []const u8,9libc_include_dir_list: []const []const u8,
2libc_installation: ?*const LibCInstallation,10libc_installation: ?*const LibCInstallation,
3libc_framework_dir_list: []const []const u8,11libc_framework_dir_list: []const []const u8,
...@@ -14,6 +22,7 @@ pub const DarwinSdkLayout = enum {...@@ -14,6 +22,7 @@ pub const DarwinSdkLayout = enum {
1422
15pub fn detect(23pub fn detect(
16 arena: Allocator,24 arena: Allocator,
25 io: Io,
17 zig_lib_dir: []const u8,26 zig_lib_dir: []const u8,
18 target: *const std.Target,27 target: *const std.Target,
19 is_native_abi: bool,28 is_native_abi: bool,
...@@ -38,7 +47,7 @@ pub fn detect(...@@ -38,7 +47,7 @@ pub fn detect(
38 // using the system libc installation.47 // using the system libc installation.
39 if (is_native_abi and !target.isMinGW()) {48 if (is_native_abi and !target.isMinGW()) {
40 const libc = try arena.create(LibCInstallation);49 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) {
42 error.CCompilerExitCode,51 error.CCompilerExitCode,
43 error.CCompilerCrashed,52 error.CCompilerCrashed,
44 error.CCompilerCannotFindHeaders,53 error.CCompilerCannotFindHeaders,
...@@ -75,7 +84,7 @@ pub fn detect(...@@ -75,7 +84,7 @@ pub fn detect(
7584
76 if (use_system_abi) {85 if (use_system_abi) {
77 const libc = try arena.create(LibCInstallation);86 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 });
79 return detectFromInstallation(arena, target, libc);88 return detectFromInstallation(arena, target, libc);
80 }89 }
8190
...@@ -265,9 +274,3 @@ fn libCGenericName(target: *const std.Target) [:0]const u8 {...@@ -265,9 +274,3 @@ fn libCGenericName(target: *const std.Target) [:0]const u8 {
265 => unreachable,274 => unreachable,
266 }275 }
267}276}
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 @@...@@ -1,4 +1,18 @@
1//! See the render function implementation for documentation of the fields.1//! 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
3include_dir: ?[]const u8 = null,17include_dir: ?[]const u8 = null,
4sys_include_dir: ?[]const u8 = null,18sys_include_dir: ?[]const u8 = null,
...@@ -23,11 +37,7 @@ pub const FindError = error{...@@ -23,11 +37,7 @@ pub const FindError = error{
23 ZigIsTheCCompiler,37 ZigIsTheCCompiler,
24};38};
2539
26pub fn parse(40pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const std.Target) !LibCInstallation {
27 allocator: Allocator,
28 libc_file: []const u8,
29 target: *const std.Target,
30) !LibCInstallation {
31 var self: LibCInstallation = .{};41 var self: LibCInstallation = .{};
3242
33 const fields = std.meta.fields(LibCInstallation);43 const fields = std.meta.fields(LibCInstallation);
...@@ -43,7 +53,7 @@ pub fn parse(...@@ -43,7 +53,7 @@ pub fn parse(
43 }53 }
44 }54 }
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)));
47 defer allocator.free(contents);57 defer allocator.free(contents);
4858
49 var it = std.mem.tokenizeScalar(u8, contents, '\n');59 var it = std.mem.tokenizeScalar(u8, contents, '\n');
...@@ -156,7 +166,6 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {...@@ -156,7 +166,6 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {
156}166}
157167
158pub const FindNativeOptions = struct {168pub const FindNativeOptions = struct {
159 allocator: Allocator,
160 target: *const std.Target,169 target: *const std.Target,
161170
162 /// If enabled, will print human-friendly errors to stderr.171 /// If enabled, will print human-friendly errors to stderr.
...@@ -164,50 +173,50 @@ pub const FindNativeOptions = struct {...@@ -164,50 +173,50 @@ pub const FindNativeOptions = struct {
164};173};
165174
166/// Finds the default, native libc.175/// Finds the default, native libc.
167pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation {176pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!LibCInstallation {
168 var self: LibCInstallation = .{};177 var self: LibCInstallation = .{};
169178
170 if (is_darwin and args.target.os.tag.isDarwin()) {179 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))
172 return error.DarwinSdkNotFound;181 return error.DarwinSdkNotFound;
173 const sdk = std.zig.system.darwin.getSdk(args.allocator, args.target) orelse182 const sdk = std.zig.system.darwin.getSdk(gpa, io, args.target) orelse
174 return error.DarwinSdkNotFound;183 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, &.{
178 sdk, "usr/include",187 sdk, "usr/include",
179 });188 });
180 self.sys_include_dir = try fs.path.join(args.allocator, &.{189 self.sys_include_dir = try fs.path.join(gpa, &.{
181 sdk, "usr/include",190 sdk, "usr/include",
182 });191 });
183 return self;192 return self;
184 } else if (is_windows) {193 } 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) {
186 error.NotFound => return error.WindowsSdkNotFound,195 error.NotFound => return error.WindowsSdkNotFound,
187 error.PathTooLong => return error.WindowsSdkNotFound,196 error.PathTooLong => return error.WindowsSdkNotFound,
188 error.OutOfMemory => return error.OutOfMemory,197 error.OutOfMemory => return error.OutOfMemory,
189 };198 };
190 defer sdk.free(args.allocator);199 defer sdk.free(gpa);
191200
192 try self.findNativeMsvcIncludeDir(args, sdk);201 try self.findNativeMsvcIncludeDir(gpa, io, sdk);
193 try self.findNativeMsvcLibDir(args, sdk);202 try self.findNativeMsvcLibDir(gpa, sdk);
194 try self.findNativeKernel32LibDir(args, sdk);203 try self.findNativeKernel32LibDir(gpa, io, args, sdk);
195 try self.findNativeIncludeDirWindows(args, sdk);204 try self.findNativeIncludeDirWindows(gpa, io, sdk);
196 try self.findNativeCrtDirWindows(args, sdk);205 try self.findNativeCrtDirWindows(gpa, io, args.target, sdk);
197 } else if (is_haiku) {206 } else if (is_haiku) {
198 try self.findNativeIncludeDirPosix(args);207 try self.findNativeIncludeDirPosix(gpa, io, args);
199 try self.findNativeGccDirHaiku(args);208 try self.findNativeGccDirHaiku(gpa, io, args);
200 self.crt_dir = try args.allocator.dupeZ(u8, "/system/develop/lib");209 self.crt_dir = try gpa.dupeZ(u8, "/system/develop/lib");
201 } else if (builtin.target.os.tag == .illumos) {210 } else if (builtin.target.os.tag == .illumos) {
202 // There is only one libc, and its headers/libraries are always in the same spot.211 // 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");212 self.include_dir = try gpa.dupeZ(u8, "/usr/include");
204 self.sys_include_dir = try args.allocator.dupeZ(u8, "/usr/include");213 self.sys_include_dir = try gpa.dupeZ(u8, "/usr/include");
205 self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib/64");214 self.crt_dir = try gpa.dupeZ(u8, "/usr/lib/64");
206 } else if (std.process.can_spawn) {215 } else if (std.process.can_spawn) {
207 try self.findNativeIncludeDirPosix(args);216 try self.findNativeIncludeDirPosix(gpa, io, args);
208 switch (builtin.target.os.tag) {217 switch (builtin.target.os.tag) {
209 .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try args.allocator.dupeZ(u8, "/usr/lib"),218 .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try gpa.dupeZ(u8, "/usr/lib"),
210 .linux => try self.findNativeCrtDirPosix(args),219 .linux => try self.findNativeCrtDirPosix(gpa, io, args),
211 else => {},220 else => {},
212 }221 }
213 } else {222 } else {
...@@ -227,11 +236,9 @@ pub fn deinit(self: *LibCInstallation, allocator: Allocator) void {...@@ -227,11 +236,9 @@ pub fn deinit(self: *LibCInstallation, allocator: Allocator) void {
227 self.* = undefined;236 self.* = undefined;
228}237}
229238
230fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void {239fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void {
231 const allocator = args.allocator;
232
233 // Detect infinite loops.240 // 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) {
235 error.Unexpected => unreachable, // WASI-only242 error.Unexpected => unreachable, // WASI-only
236 else => |e| return e,243 else => |e| return e,
237 };244 };
...@@ -250,7 +257,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -250,7 +257,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
250257
251 const dev_null = if (is_windows) "nul" else "/dev/null";258 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);
254 defer argv.deinit();261 defer argv.deinit();
255262
256 try appendCcExe(&argv, skip_cc_env_var);263 try appendCcExe(&argv, skip_cc_env_var);
...@@ -261,8 +268,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -261,8 +268,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
261 dev_null,268 dev_null,
262 });269 });
263270
264 const run_res = std.process.Child.run(.{271 const run_res = std.process.Child.run(gpa, io, .{
265 .allocator = allocator,
266 .argv = argv.items,272 .argv = argv.items,
267 .max_output_bytes = 1024 * 1024,273 .max_output_bytes = 1024 * 1024,
268 .env_map = &env_map,274 .env_map = &env_map,
...@@ -279,8 +285,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -279,8 +285,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
279 },285 },
280 };286 };
281 defer {287 defer {
282 allocator.free(run_res.stdout);288 gpa.free(run_res.stdout);
283 allocator.free(run_res.stderr);289 gpa.free(run_res.stderr);
284 }290 }
285 switch (run_res.term) {291 switch (run_res.term) {
286 .Exited => |code| if (code != 0) {292 .Exited => |code| if (code != 0) {
...@@ -294,7 +300,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -294,7 +300,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
294 }300 }
295301
296 var it = std.mem.tokenizeAny(u8, run_res.stderr, "\n\r");302 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);
298 defer search_paths.deinit();304 defer search_paths.deinit();
299 while (it.next()) |line| {305 while (it.next()) |line| {
300 if (line.len != 0 and line[0] == ' ') {306 if (line.len != 0 and line[0] == ' ') {
...@@ -318,7 +324,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -318,7 +324,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
318 // search in reverse order324 // search in reverse order
319 const search_path_untrimmed = search_paths.items[search_paths.items.len - path_i - 1];325 const search_path_untrimmed = search_paths.items[search_paths.items.len - path_i - 1];
320 const search_path = std.mem.trimStart(u8, search_path_untrimmed, " ");326 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) {
322 error.FileNotFound,328 error.FileNotFound,
323 error.NotDir,329 error.NotDir,
324 error.NoDevice,330 error.NoDevice,
...@@ -326,11 +332,11 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -326,11 +332,11 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
326332
327 else => return error.FileSystem,333 else => return error.FileSystem,
328 };334 };
329 defer search_dir.close();335 defer search_dir.close(io);
330336
331 if (self.include_dir == null) {337 if (self.include_dir == null) {
332 if (search_dir.access(include_dir_example_file, .{})) |_| {338 if (search_dir.access(io, include_dir_example_file, .{})) |_| {
333 self.include_dir = try allocator.dupeZ(u8, search_path);339 self.include_dir = try gpa.dupeZ(u8, search_path);
334 } else |err| switch (err) {340 } else |err| switch (err) {
335 error.FileNotFound => {},341 error.FileNotFound => {},
336 else => return error.FileSystem,342 else => return error.FileSystem,
...@@ -338,8 +344,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -338,8 +344,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
338 }344 }
339345
340 if (self.sys_include_dir == null) {346 if (self.sys_include_dir == null) {
341 if (search_dir.access(sys_include_dir_example_file, .{})) |_| {347 if (search_dir.access(io, sys_include_dir_example_file, .{})) |_| {
342 self.sys_include_dir = try allocator.dupeZ(u8, search_path);348 self.sys_include_dir = try gpa.dupeZ(u8, search_path);
343 } else |err| switch (err) {349 } else |err| switch (err) {
344 error.FileNotFound => {},350 error.FileNotFound => {},
345 else => return error.FileSystem,351 else => return error.FileSystem,
...@@ -357,22 +363,21 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -357,22 +363,21 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
357363
358fn findNativeIncludeDirWindows(364fn findNativeIncludeDirWindows(
359 self: *LibCInstallation,365 self: *LibCInstallation,
360 args: FindNativeOptions,366 gpa: Allocator,
367 io: Io,
361 sdk: std.zig.WindowsSdk,368 sdk: std.zig.WindowsSdk,
362) FindError!void {369) FindError!void {
363 const allocator = args.allocator;
364
365 var install_buf: [2]std.zig.WindowsSdk.Installation = undefined;370 var install_buf: [2]std.zig.WindowsSdk.Installation = undefined;
366 const installs = fillInstallations(&install_buf, sdk);371 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);
369 defer result_buf.deinit();374 defer result_buf.deinit();
370375
371 for (installs) |install| {376 for (installs) |install| {
372 result_buf.shrinkAndFree(0);377 result_buf.shrinkAndFree(0);
373 try result_buf.print("{s}\\Include\\{s}\\ucrt", .{ install.path, install.version });378 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) {
376 error.FileNotFound,381 error.FileNotFound,
377 error.NotDir,382 error.NotDir,
378 error.NoDevice,383 error.NoDevice,
...@@ -380,9 +385,9 @@ fn findNativeIncludeDirWindows(...@@ -380,9 +385,9 @@ fn findNativeIncludeDirWindows(
380385
381 else => return error.FileSystem,386 else => return error.FileSystem,
382 };387 };
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) {
386 error.FileNotFound => continue,391 error.FileNotFound => continue,
387 else => return error.FileSystem,392 else => return error.FileSystem,
388 };393 };
...@@ -396,18 +401,18 @@ fn findNativeIncludeDirWindows(...@@ -396,18 +401,18 @@ fn findNativeIncludeDirWindows(
396401
397fn findNativeCrtDirWindows(402fn findNativeCrtDirWindows(
398 self: *LibCInstallation,403 self: *LibCInstallation,
399 args: FindNativeOptions,404 gpa: Allocator,
405 io: Io,
406 target: *const std.Target,
400 sdk: std.zig.WindowsSdk,407 sdk: std.zig.WindowsSdk,
401) FindError!void {408) FindError!void {
402 const allocator = args.allocator;
403
404 var install_buf: [2]std.zig.WindowsSdk.Installation = undefined;409 var install_buf: [2]std.zig.WindowsSdk.Installation = undefined;
405 const installs = fillInstallations(&install_buf, sdk);410 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);
408 defer result_buf.deinit();413 defer result_buf.deinit();
409414
410 const arch_sub_dir = switch (args.target.cpu.arch) {415 const arch_sub_dir = switch (target.cpu.arch) {
411 .x86 => "x86",416 .x86 => "x86",
412 .x86_64 => "x64",417 .x86_64 => "x64",
413 .arm, .armeb => "arm",418 .arm, .armeb => "arm",
...@@ -419,7 +424,7 @@ fn findNativeCrtDirWindows(...@@ -419,7 +424,7 @@ fn findNativeCrtDirWindows(
419 result_buf.shrinkAndFree(0);424 result_buf.shrinkAndFree(0);
420 try result_buf.print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ install.path, install.version, arch_sub_dir });425 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) {
423 error.FileNotFound,428 error.FileNotFound,
424 error.NotDir,429 error.NotDir,
425 error.NoDevice,430 error.NoDevice,
...@@ -427,9 +432,9 @@ fn findNativeCrtDirWindows(...@@ -427,9 +432,9 @@ fn findNativeCrtDirWindows(
427432
428 else => return error.FileSystem,433 else => return error.FileSystem,
429 };434 };
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) {
433 error.FileNotFound => continue,438 error.FileNotFound => continue,
434 else => return error.FileSystem,439 else => return error.FileSystem,
435 };440 };
...@@ -440,9 +445,8 @@ fn findNativeCrtDirWindows(...@@ -440,9 +445,8 @@ fn findNativeCrtDirWindows(
440 return error.LibCRuntimeNotFound;445 return error.LibCRuntimeNotFound;
441}446}
442447
443fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void {448fn findNativeCrtDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void {
444 self.crt_dir = try ccPrintFileName(.{449 self.crt_dir = try ccPrintFileName(gpa, io, .{
445 .allocator = args.allocator,
446 .search_basename = switch (args.target.os.tag) {450 .search_basename = switch (args.target.os.tag) {
447 .linux => if (args.target.abi.isAndroid()) "crtbegin_dynamic.o" else "crt1.o",451 .linux => if (args.target.abi.isAndroid()) "crtbegin_dynamic.o" else "crt1.o",
448 else => "crt1.o",452 else => "crt1.o",
...@@ -452,9 +456,8 @@ fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindE...@@ -452,9 +456,8 @@ fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindE
452 });456 });
453}457}
454458
455fn findNativeGccDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindError!void {459fn findNativeGccDirHaiku(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void {
456 self.gcc_dir = try ccPrintFileName(.{460 self.gcc_dir = try ccPrintFileName(gpa, io, .{
457 .allocator = args.allocator,
458 .search_basename = "crtbeginS.o",461 .search_basename = "crtbeginS.o",
459 .want_dirname = .only_dir,462 .want_dirname = .only_dir,
460 .verbose = args.verbose,463 .verbose = args.verbose,
...@@ -463,15 +466,15 @@ fn findNativeGccDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindE...@@ -463,15 +466,15 @@ fn findNativeGccDirHaiku(self: *LibCInstallation, args: FindNativeOptions) FindE
463466
464fn findNativeKernel32LibDir(467fn findNativeKernel32LibDir(
465 self: *LibCInstallation,468 self: *LibCInstallation,
469 gpa: Allocator,
470 io: Io,
466 args: FindNativeOptions,471 args: FindNativeOptions,
467 sdk: std.zig.WindowsSdk,472 sdk: std.zig.WindowsSdk,
468) FindError!void {473) FindError!void {
469 const allocator = args.allocator;
470
471 var install_buf: [2]std.zig.WindowsSdk.Installation = undefined;474 var install_buf: [2]std.zig.WindowsSdk.Installation = undefined;
472 const installs = fillInstallations(&install_buf, sdk);475 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);
475 defer result_buf.deinit();478 defer result_buf.deinit();
476479
477 const arch_sub_dir = switch (args.target.cpu.arch) {480 const arch_sub_dir = switch (args.target.cpu.arch) {
...@@ -486,7 +489,7 @@ fn findNativeKernel32LibDir(...@@ -486,7 +489,7 @@ fn findNativeKernel32LibDir(
486 result_buf.shrinkAndFree(0);489 result_buf.shrinkAndFree(0);
487 try result_buf.print("{s}\\Lib\\{s}\\um\\{s}", .{ install.path, install.version, arch_sub_dir });490 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) {
490 error.FileNotFound,493 error.FileNotFound,
491 error.NotDir,494 error.NotDir,
492 error.NoDevice,495 error.NoDevice,
...@@ -494,9 +497,9 @@ fn findNativeKernel32LibDir(...@@ -494,9 +497,9 @@ fn findNativeKernel32LibDir(
494497
495 else => return error.FileSystem,498 else => return error.FileSystem,
496 };499 };
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) {
500 error.FileNotFound => continue,503 error.FileNotFound => continue,
501 else => return error.FileSystem,504 else => return error.FileSystem,
502 };505 };
...@@ -509,19 +512,18 @@ fn findNativeKernel32LibDir(...@@ -509,19 +512,18 @@ fn findNativeKernel32LibDir(
509512
510fn findNativeMsvcIncludeDir(513fn findNativeMsvcIncludeDir(
511 self: *LibCInstallation,514 self: *LibCInstallation,
512 args: FindNativeOptions,515 gpa: Allocator,
516 io: Io,
513 sdk: std.zig.WindowsSdk,517 sdk: std.zig.WindowsSdk,
514) FindError!void {518) FindError!void {
515 const allocator = args.allocator;
516
517 const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCStdLibHeaderNotFound;519 const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCStdLibHeaderNotFound;
518 const up1 = fs.path.dirname(msvc_lib_dir) orelse return error.LibCStdLibHeaderNotFound;520 const up1 = fs.path.dirname(msvc_lib_dir) orelse return error.LibCStdLibHeaderNotFound;
519 const up2 = fs.path.dirname(up1) orelse return error.LibCStdLibHeaderNotFound;521 const up2 = fs.path.dirname(up1) orelse return error.LibCStdLibHeaderNotFound;
520522
521 const dir_path = try fs.path.join(allocator, &[_][]const u8{ up2, "include" });523 const dir_path = try fs.path.join(gpa, &[_][]const u8{ up2, "include" });
522 errdefer allocator.free(dir_path);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) {
525 error.FileNotFound,527 error.FileNotFound,
526 error.NotDir,528 error.NotDir,
527 error.NoDevice,529 error.NoDevice,
...@@ -529,9 +531,9 @@ fn findNativeMsvcIncludeDir(...@@ -529,9 +531,9 @@ fn findNativeMsvcIncludeDir(
529531
530 else => return error.FileSystem,532 else => return error.FileSystem,
531 };533 };
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) {
535 error.FileNotFound => return error.LibCStdLibHeaderNotFound,537 error.FileNotFound => return error.LibCStdLibHeaderNotFound,
536 else => return error.FileSystem,538 else => return error.FileSystem,
537 };539 };
...@@ -541,27 +543,23 @@ fn findNativeMsvcIncludeDir(...@@ -541,27 +543,23 @@ fn findNativeMsvcIncludeDir(
541543
542fn findNativeMsvcLibDir(544fn findNativeMsvcLibDir(
543 self: *LibCInstallation,545 self: *LibCInstallation,
544 args: FindNativeOptions,546 gpa: Allocator,
545 sdk: std.zig.WindowsSdk,547 sdk: std.zig.WindowsSdk,
546) FindError!void {548) FindError!void {
547 const allocator = args.allocator;
548 const msvc_lib_dir = sdk.msvc_lib_dir orelse return error.LibCRuntimeNotFound;549 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);
550}551}
551552
552pub const CCPrintFileNameOptions = struct {553pub const CCPrintFileNameOptions = struct {
553 allocator: Allocator,
554 search_basename: []const u8,554 search_basename: []const u8,
555 want_dirname: enum { full_path, only_dir },555 want_dirname: enum { full_path, only_dir },
556 verbose: bool = false,556 verbose: bool = false,
557};557};
558558
559/// caller owns returned memory559/// caller owns returned memory
560fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {560fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 {
561 const allocator = args.allocator;
562
563 // Detect infinite loops.561 // 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) {
565 error.Unexpected => unreachable, // WASI-only563 error.Unexpected => unreachable, // WASI-only
566 else => |e| return e,564 else => |e| return e,
567 };565 };
...@@ -578,17 +576,16 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {...@@ -578,17 +576,16 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
578 break :blk false;576 break :blk false;
579 };577 };
580578
581 var argv = std.array_list.Managed([]const u8).init(allocator);579 var argv = std.array_list.Managed([]const u8).init(gpa);
582 defer argv.deinit();580 defer argv.deinit();
583581
584 const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={s}", .{args.search_basename});582 const arg1 = try std.fmt.allocPrint(gpa, "-print-file-name={s}", .{args.search_basename});
585 defer allocator.free(arg1);583 defer gpa.free(arg1);
586584
587 try appendCcExe(&argv, skip_cc_env_var);585 try appendCcExe(&argv, skip_cc_env_var);
588 try argv.append(arg1);586 try argv.append(arg1);
589587
590 const run_res = std.process.Child.run(.{588 const run_res = std.process.Child.run(gpa, io, .{
591 .allocator = allocator,
592 .argv = argv.items,589 .argv = argv.items,
593 .max_output_bytes = 1024 * 1024,590 .max_output_bytes = 1024 * 1024,
594 .env_map = &env_map,591 .env_map = &env_map,
...@@ -602,8 +599,8 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {...@@ -602,8 +599,8 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
602 else => return error.UnableToSpawnCCompiler,599 else => return error.UnableToSpawnCCompiler,
603 };600 };
604 defer {601 defer {
605 allocator.free(run_res.stdout);602 gpa.free(run_res.stdout);
606 allocator.free(run_res.stderr);603 gpa.free(run_res.stderr);
607 }604 }
608 switch (run_res.term) {605 switch (run_res.term) {
609 .Exited => |code| if (code != 0) {606 .Exited => |code| if (code != 0) {
...@@ -622,10 +619,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {...@@ -622,10 +619,10 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
622 // So we detect failure by checking if the output matches exactly the input.619 // So we detect failure by checking if the output matches exactly the input.
623 if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound;620 if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound;
624 switch (args.want_dirname) {621 switch (args.want_dirname) {
625 .full_path => return allocator.dupeZ(u8, line),622 .full_path => return gpa.dupeZ(u8, line),
626 .only_dir => {623 .only_dir => {
627 const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound;624 const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound;
628 return allocator.dupeZ(u8, dirname);625 return gpa.dupeZ(u8, dirname);
629 },626 },
630 }627 }
631}628}
...@@ -1015,17 +1012,3 @@ pub fn resolveCrtPaths(...@@ -1015,17 +1012,3 @@ pub fn resolveCrtPaths(
1015 },1012 },
1016 }1013 }
1017}1014}
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 @@...@@ -1,7 +1,11 @@
1const WindowsSdk = @This();1const WindowsSdk = @This();
2const builtin = @import("builtin");2const builtin = @import("builtin");
3
3const std = @import("std");4const std = @import("std");
5const Io = std.Io;
6const Dir = std.Io.Dir;
4const Writer = std.Io.Writer;7const Writer = std.Io.Writer;
8const Allocator = std.mem.Allocator;
59
6windows10sdk: ?Installation,10windows10sdk: ?Installation,
7windows81sdk: ?Installation,11windows81sdk: ?Installation,
...@@ -19,8 +23,8 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len...@@ -19,8 +23,8 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len
1923
20/// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory.24/// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory.
21/// Caller owns the result's fields.25/// Caller owns the result's fields.
22/// After finishing work, call `free(allocator)`.26/// Returns memory allocated by `gpa`
23pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk {27pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk {
24 if (builtin.os.tag != .windows) return error.NotFound;28 if (builtin.os.tag != .windows) return error.NotFound;
2529
26 //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed30 //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...@@ -29,27 +33,27 @@ pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutO
29 };33 };
30 defer roots_key.closeKey();34 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) {
33 error.InstallationNotFound => null,37 error.InstallationNotFound => null,
34 error.PathTooLong => null,38 error.PathTooLong => null,
35 error.VersionTooLong => null,39 error.VersionTooLong => null,
36 error.OutOfMemory => return error.OutOfMemory,40 error.OutOfMemory => return error.OutOfMemory,
37 };41 };
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) {
41 error.InstallationNotFound => null,45 error.InstallationNotFound => null,
42 error.PathTooLong => null,46 error.PathTooLong => null,
43 error.VersionTooLong => null,47 error.VersionTooLong => null,
44 error.OutOfMemory => return error.OutOfMemory,48 error.OutOfMemory => return error.OutOfMemory,
45 };49 };
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) {
49 error.MsvcLibDirNotFound => null,53 error.MsvcLibDirNotFound => null,
50 error.OutOfMemory => return error.OutOfMemory,54 error.OutOfMemory => return error.OutOfMemory,
51 };55 };
52 errdefer allocator.free(msvc_lib_dir);56 errdefer gpa.free(msvc_lib_dir);
5357
54 return .{58 return .{
55 .windows10sdk = windows10sdk,59 .windows10sdk = windows10sdk,
...@@ -58,15 +62,15 @@ pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutO...@@ -58,15 +62,15 @@ pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutO
58 };62 };
59}63}
6064
61pub fn free(sdk: WindowsSdk, allocator: std.mem.Allocator) void {65pub fn free(sdk: WindowsSdk, gpa: Allocator) void {
62 if (sdk.windows10sdk) |*w10sdk| {66 if (sdk.windows10sdk) |*w10sdk| {
63 w10sdk.free(allocator);67 w10sdk.free(gpa);
64 }68 }
65 if (sdk.windows81sdk) |*w81sdk| {69 if (sdk.windows81sdk) |*w81sdk| {
66 w81sdk.free(allocator);70 w81sdk.free(gpa);
67 }71 }
68 if (sdk.msvc_lib_dir) |msvc_lib_dir| {72 if (sdk.msvc_lib_dir) |msvc_lib_dir| {
69 allocator.free(msvc_lib_dir);73 gpa.free(msvc_lib_dir);
70 }74 }
71}75}
7276
...@@ -74,8 +78,9 @@ pub fn free(sdk: WindowsSdk, allocator: std.mem.Allocator) void {...@@ -74,8 +78,9 @@ pub fn free(sdk: WindowsSdk, allocator: std.mem.Allocator) void {
74/// and a version. Returns slice of version strings sorted in descending order.78/// and a version. Returns slice of version strings sorted in descending order.
75/// Caller owns result.79/// Caller owns result.
76fn iterateAndFilterByVersion(80fn iterateAndFilterByVersion(
77 iterator: *std.fs.Dir.Iterator,81 iterator: *Dir.Iterator,
78 allocator: std.mem.Allocator,82 gpa: Allocator,
83 io: Io,
79 prefix: []const u8,84 prefix: []const u8,
80) error{OutOfMemory}![][]const u8 {85) error{OutOfMemory}![][]const u8 {
81 const Version = struct {86 const Version = struct {
...@@ -92,15 +97,15 @@ fn iterateAndFilterByVersion(...@@ -92,15 +97,15 @@ fn iterateAndFilterByVersion(
92 std.mem.order(u8, lhs.build, rhs.build);97 std.mem.order(u8, lhs.build, rhs.build);
93 }98 }
94 };99 };
95 var versions = std.array_list.Managed(Version).init(allocator);100 var versions = std.array_list.Managed(Version).init(gpa);
96 var dirs = std.array_list.Managed([]const u8).init(allocator);101 var dirs = std.array_list.Managed([]const u8).init(gpa);
97 defer {102 defer {
98 versions.deinit();103 versions.deinit();
99 for (dirs.items) |filtered_dir| allocator.free(filtered_dir);104 for (dirs.items) |filtered_dir| gpa.free(filtered_dir);
100 dirs.deinit();105 dirs.deinit();
101 }106 }
102107
103 iterate: while (iterator.next() catch null) |entry| {108 iterate: while (iterator.next(io) catch null) |entry| {
104 if (entry.kind != .directory) continue;109 if (entry.kind != .directory) continue;
105 if (!std.mem.startsWith(u8, entry.name, prefix)) continue;110 if (!std.mem.startsWith(u8, entry.name, prefix)) continue;
106111
...@@ -116,8 +121,8 @@ fn iterateAndFilterByVersion(...@@ -116,8 +121,8 @@ fn iterateAndFilterByVersion(
116 num.* = Version.parseNum(num_it.next() orelse break) orelse continue :iterate121 num.* = Version.parseNum(num_it.next() orelse break) orelse continue :iterate
117 else if (num_it.next()) |_| continue;122 else if (num_it.next()) |_| continue;
118123
119 const name = try allocator.dupe(u8, suffix);124 const name = try gpa.dupe(u8, suffix);
120 errdefer allocator.free(name);125 errdefer gpa.free(name);
121 if (underscore) |pos| version.build = name[pos + 1 ..];126 if (underscore) |pos| version.build = name[pos + 1 ..];
122127
123 try versions.append(version);128 try versions.append(version);
...@@ -174,7 +179,7 @@ const RegistryWtf8 = struct {...@@ -174,7 +179,7 @@ const RegistryWtf8 = struct {
174179
175 /// Get string from registry.180 /// Get string from registry.
176 /// Caller owns result.181 /// 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 {
178 const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {183 const subkey_wtf16le: [:0]const u16 = subkey_wtf16le: {
179 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;184 var subkey_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined;
180 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;185 const subkey_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(subkey_wtf16le_buf[0..], subkey) catch unreachable;
...@@ -190,11 +195,11 @@ const RegistryWtf8 = struct {...@@ -190,11 +195,11 @@ const RegistryWtf8 = struct {
190 };195 };
191196
192 const registry_wtf16le: RegistryWtf16Le = .{ .key = reg.key };197 const registry_wtf16le: RegistryWtf16Le = .{ .key = reg.key };
193 const value_wtf16le = try registry_wtf16le.getString(allocator, subkey_wtf16le, value_name_wtf16le);198 const value_wtf16le = try registry_wtf16le.getString(gpa, subkey_wtf16le, value_name_wtf16le);
194 defer allocator.free(value_wtf16le);199 defer gpa.free(value_wtf16le);
195200
196 const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_wtf16le);201 const value_wtf8: []u8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, value_wtf16le);
197 errdefer allocator.free(value_wtf8);202 errdefer gpa.free(value_wtf8);
198203
199 return value_wtf8;204 return value_wtf8;
200 }205 }
...@@ -282,7 +287,7 @@ const RegistryWtf16Le = struct {...@@ -282,7 +287,7 @@ const RegistryWtf16Le = struct {
282 }287 }
283288
284 /// Get string ([:0]const u16) from registry.289 /// 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 {
286 var actual_type: windows.ULONG = undefined;291 var actual_type: windows.ULONG = undefined;
287292
288 // Calculating length to allocate293 // Calculating length to allocate
...@@ -311,8 +316,8 @@ const RegistryWtf16Le = struct {...@@ -311,8 +316,8 @@ const RegistryWtf16Le = struct {
311 else => return error.NotAString,316 else => return error.NotAString,
312 }317 }
313318
314 const value_wtf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);319 const value_wtf16le_buf: []u16 = try gpa.alloc(u16, std.math.divCeil(u32, value_wtf16le_buf_size, 2) catch unreachable);
315 errdefer allocator.free(value_wtf16le_buf);320 errdefer gpa.free(value_wtf16le_buf);
316321
317 return_code_int = windows.advapi32.RegGetValueW(322 return_code_int = windows.advapi32.RegGetValueW(
318 reg.key,323 reg.key,
...@@ -346,7 +351,7 @@ const RegistryWtf16Le = struct {...@@ -346,7 +351,7 @@ const RegistryWtf16Le = struct {
346 break :value_wtf16le std.mem.span(value_wtf16le_overestimated);351 break :value_wtf16le std.mem.span(value_wtf16le_overestimated);
347 };352 };
348353
349 _ = allocator.resize(value_wtf16le_buf, value_wtf16le.len);354 _ = gpa.resize(value_wtf16le_buf, value_wtf16le.len);
350 return value_wtf16le;355 return value_wtf16le;
351 }356 }
352357
...@@ -414,88 +419,89 @@ pub const Installation = struct {...@@ -414,88 +419,89 @@ pub const Installation = struct {
414419
415 /// Find path and version of Windows SDK.420 /// Find path and version of Windows SDK.
416 /// Caller owns the result's fields.421 /// Caller owns the result's fields.
417 /// After finishing work, call `free(allocator)`.
418 fn find(422 fn find(
419 allocator: std.mem.Allocator,423 gpa: Allocator,
424 io: Io,
420 roots_key: RegistryWtf8,425 roots_key: RegistryWtf8,
421 roots_subkey: []const u8,426 roots_subkey: []const u8,
422 prefix: []const u8,427 prefix: []const u8,
423 version_key_name: []const u8,428 version_key_name: []const u8,
424 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {429 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
425 roots: {430 roots: {
426 const installation = findFromRoot(allocator, roots_key, roots_subkey, prefix) catch431 const installation = findFromRoot(gpa, io, roots_key, roots_subkey, prefix) catch
427 break :roots;432 break :roots;
428 if (installation.isValidVersion()) return installation;433 if (installation.isValidVersion()) return installation;
429 installation.free(allocator);434 installation.free(gpa);
430 }435 }
431 {436 {
432 const installation = try findFromInstallationFolder(allocator, version_key_name);437 const installation = try findFromInstallationFolder(gpa, version_key_name);
433 if (installation.isValidVersion()) return installation;438 if (installation.isValidVersion()) return installation;
434 installation.free(allocator);439 installation.free(gpa);
435 }440 }
436 return error.InstallationNotFound;441 return error.InstallationNotFound;
437 }442 }
438443
439 fn findFromRoot(444 fn findFromRoot(
440 allocator: std.mem.Allocator,445 gpa: Allocator,
446 io: Io,
441 roots_key: RegistryWtf8,447 roots_key: RegistryWtf8,
442 roots_subkey: []const u8,448 roots_subkey: []const u8,
443 prefix: []const u8,449 prefix: []const u8,
444 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {450 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
445 const path = path: {451 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) {
447 error.NotAString => return error.InstallationNotFound,453 error.NotAString => return error.InstallationNotFound,
448 error.ValueNameNotFound => return error.InstallationNotFound,454 error.ValueNameNotFound => return error.InstallationNotFound,
449 error.StringNotFound => return error.InstallationNotFound,455 error.StringNotFound => return error.InstallationNotFound,
450456
451 error.OutOfMemory => return error.OutOfMemory,457 error.OutOfMemory => return error.OutOfMemory,
452 };458 };
453 if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(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)) {
454 allocator.free(path_maybe_with_trailing_slash);460 gpa.free(path_maybe_with_trailing_slash);
455 return error.PathTooLong;461 return error.PathTooLong;
456 }462 }
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);
459 errdefer path.deinit();465 errdefer path.deinit();
460466
461 // String might contain trailing slash, so trim it here467 // String might contain trailing slash, so trim it here
462 if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop();468 if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop();
463 break :path try path.toOwnedSlice();469 break :path try path.toOwnedSlice();
464 };470 };
465 errdefer allocator.free(path);471 errdefer gpa.free(path);
466472
467 const version = version: {473 const version = version: {
468 var buf: [std.fs.max_path_bytes]u8 = undefined;474 var buf: [Dir.max_path_bytes]u8 = undefined;
469 const sdk_lib_dir_path = std.fmt.bufPrint(buf[0..], "{s}\\Lib\\", .{path}) catch |err| switch (err) {475 const sdk_lib_dir_path = std.fmt.bufPrint(buf[0..], "{s}\\Lib\\", .{path}) catch |err| switch (err) {
470 error.NoSpaceLeft => return error.PathTooLong,476 error.NoSpaceLeft => return error.PathTooLong,
471 };477 };
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
474 // enumerate files in sdk path looking for latest version480 // 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, .{
476 .iterate = true,482 .iterate = true,
477 }) catch |err| switch (err) {483 }) catch |err| switch (err) {
478 error.NameTooLong => return error.PathTooLong,484 error.NameTooLong => return error.PathTooLong,
479 else => return error.InstallationNotFound,485 else => return error.InstallationNotFound,
480 };486 };
481 defer sdk_lib_dir.close();487 defer sdk_lib_dir.close(io);
482488
483 var iterator = sdk_lib_dir.iterate();489 var iterator = sdk_lib_dir.iterate();
484 const versions = try iterateAndFilterByVersion(&iterator, allocator, prefix);490 const versions = try iterateAndFilterByVersion(&iterator, gpa, io, prefix);
485 if (versions.len == 0) return error.InstallationNotFound;491 if (versions.len == 0) return error.InstallationNotFound;
486 defer {492 defer {
487 for (versions[1..]) |version| allocator.free(version);493 for (versions[1..]) |version| gpa.free(version);
488 allocator.free(versions);494 gpa.free(versions);
489 }495 }
490 break :version versions[0];496 break :version versions[0];
491 };497 };
492 errdefer allocator.free(version);498 errdefer gpa.free(version);
493499
494 return .{ .path = path, .version = version };500 return .{ .path = path, .version = version };
495 }501 }
496502
497 fn findFromInstallationFolder(503 fn findFromInstallationFolder(
498 allocator: std.mem.Allocator,504 gpa: Allocator,
499 version_key_name: []const u8,505 version_key_name: []const u8,
500 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {506 ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
501 var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined;507 var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined;
...@@ -514,7 +520,7 @@ pub const Installation = struct {...@@ -514,7 +520,7 @@ pub const Installation = struct {
514 defer key.closeKey();520 defer key.closeKey();
515521
516 const path: []const u8 = path: {522 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) {
518 error.NotAString => return error.InstallationNotFound,524 error.NotAString => return error.InstallationNotFound,
519 error.ValueNameNotFound => return error.InstallationNotFound,525 error.ValueNameNotFound => return error.InstallationNotFound,
520 error.StringNotFound => return error.InstallationNotFound,526 error.StringNotFound => return error.InstallationNotFound,
...@@ -522,12 +528,12 @@ pub const Installation = struct {...@@ -522,12 +528,12 @@ pub const Installation = struct {
522 error.OutOfMemory => return error.OutOfMemory,528 error.OutOfMemory => return error.OutOfMemory,
523 };529 };
524530
525 if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(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)) {
526 allocator.free(path_maybe_with_trailing_slash);532 gpa.free(path_maybe_with_trailing_slash);
527 return error.PathTooLong;533 return error.PathTooLong;
528 }534 }
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);
531 errdefer path.deinit();537 errdefer path.deinit();
532538
533 // String might contain trailing slash, so trim it here539 // String might contain trailing slash, so trim it here
...@@ -536,12 +542,12 @@ pub const Installation = struct {...@@ -536,12 +542,12 @@ pub const Installation = struct {
536 const path_without_trailing_slash = try path.toOwnedSlice();542 const path_without_trailing_slash = try path.toOwnedSlice();
537 break :path path_without_trailing_slash;543 break :path path_without_trailing_slash;
538 };544 };
539 errdefer allocator.free(path);545 errdefer gpa.free(path);
540546
541 const version: []const u8 = version: {547 const version: []const u8 = version: {
542548
543 // note(dimenus): Microsoft doesn't include the .0 in the ProductVersion key....549 // 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) {
545 error.NotAString => return error.InstallationNotFound,551 error.NotAString => return error.InstallationNotFound,
546 error.ValueNameNotFound => return error.InstallationNotFound,552 error.ValueNameNotFound => return error.InstallationNotFound,
547 error.StringNotFound => return error.InstallationNotFound,553 error.StringNotFound => return error.InstallationNotFound,
...@@ -549,11 +555,11 @@ pub const Installation = struct {...@@ -549,11 +555,11 @@ pub const Installation = struct {
549 error.OutOfMemory => return error.OutOfMemory,555 error.OutOfMemory => return error.OutOfMemory,
550 };556 };
551 if (version_without_0.len + ".0".len > product_version_max_length) {557 if (version_without_0.len + ".0".len > product_version_max_length) {
552 allocator.free(version_without_0);558 gpa.free(version_without_0);
553 return error.VersionTooLong;559 return error.VersionTooLong;
554 }560 }
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);
557 errdefer version.deinit();563 errdefer version.deinit();
558564
559 try version.appendSlice(".0");565 try version.appendSlice(".0");
...@@ -561,14 +567,14 @@ pub const Installation = struct {...@@ -561,14 +567,14 @@ pub const Installation = struct {
561 const version_with_0 = try version.toOwnedSlice();567 const version_with_0 = try version.toOwnedSlice();
562 break :version version_with_0;568 break :version version_with_0;
563 };569 };
564 errdefer allocator.free(version);570 errdefer gpa.free(version);
565571
566 return .{ .path = path, .version = version };572 return .{ .path = path, .version = version };
567 }573 }
568574
569 /// Check whether this version is enumerated in registry.575 /// Check whether this version is enumerated in registry.
570 fn isValidVersion(installation: Installation) bool {576 fn isValidVersion(installation: Installation) bool {
571 var buf: [std.fs.max_path_bytes]u8 = undefined;577 var buf: [Dir.max_path_bytes]u8 = undefined;
572 const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{578 const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{
573 windows_kits_reg_key,579 windows_kits_reg_key,
574 installation.version,580 installation.version,
...@@ -597,21 +603,21 @@ pub const Installation = struct {...@@ -597,21 +603,21 @@ pub const Installation = struct {
597 return (reg_value == 1);603 return (reg_value == 1);
598 }604 }
599605
600 fn free(install: Installation, allocator: std.mem.Allocator) void {606 fn free(install: Installation, gpa: Allocator) void {
601 allocator.free(install.path);607 gpa.free(install.path);
602 allocator.free(install.version);608 gpa.free(install.version);
603 }609 }
604};610};
605611
606const MsvcLibDir = struct {612const 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 {
608 const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup";614 const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup";
609 const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) {615 const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) {
610 error.KeyNotFound => return error.PathNotFound,616 error.KeyNotFound => return error.PathNotFound,
611 };617 };
612 defer vs_setup_key.closeKey();618 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) {
615 error.NotAString,621 error.NotAString,
616 error.ValueNameNotFound,622 error.ValueNameNotFound,
617 error.StringNotFound,623 error.StringNotFound,
...@@ -619,24 +625,24 @@ const MsvcLibDir = struct {...@@ -619,24 +625,24 @@ const MsvcLibDir = struct {
619625
620 error.OutOfMemory => return error.OutOfMemory,626 error.OutOfMemory => return error.OutOfMemory,
621 };627 };
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" });632 const instances_path = try Dir.path.join(gpa, &.{ packages_path, "_Instances" });
627 defer allocator.free(instances_path);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;
630 }636 }
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 {
633 const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}";639 const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}";
634 const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid, .{}) catch |err| switch (err) {640 const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid, .{}) catch |err| switch (err) {
635 error.KeyNotFound => return error.PathNotFound,641 error.KeyNotFound => return error.PathNotFound,
636 };642 };
637 defer setup_config_key.closeKey();643 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) {
640 error.NotAString,646 error.NotAString,
641 error.ValueNameNotFound,647 error.ValueNameNotFound,
642 error.StringNotFound,648 error.StringNotFound,
...@@ -644,11 +650,11 @@ const MsvcLibDir = struct {...@@ -644,11 +650,11 @@ const MsvcLibDir = struct {
644650
645 error.OutOfMemory => return error.OutOfMemory,651 error.OutOfMemory => return error.OutOfMemory,
646 };652 };
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);
652 // the .dll filename658 // the .dll filename
653 _ = path_it.last();659 _ = path_it.last();
654 const root_path = while (path_it.previous()) |dir_component| {660 const root_path = while (path_it.previous()) |dir_component| {
...@@ -659,17 +665,17 @@ const MsvcLibDir = struct {...@@ -659,17 +665,17 @@ const MsvcLibDir = struct {
659 return error.PathNotFound;665 return error.PathNotFound;
660 };666 };
661667
662 const instances_path = try std.fs.path.join(allocator, &.{ root_path, "Packages", "_Instances" });668 const instances_path = try Dir.path.join(gpa, &.{ root_path, "Packages", "_Instances" });
663 defer allocator.free(instances_path);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;
666 }672 }
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 {
669 // First, try getting the packages cache path from the registry.675 // First, try getting the packages cache path from the registry.
670 // This only seems to exist when the path is different from the default.676 // This only seems to exist when the path is different from the default.
671 method1: {677 method1: {
672 return findInstancesDirViaSetup(allocator) catch |err| switch (err) {678 return findInstancesDirViaSetup(gpa, io) catch |err| switch (err) {
673 error.OutOfMemory => |e| return e,679 error.OutOfMemory => |e| return e,
674 error.PathNotFound => break :method1,680 error.PathNotFound => break :method1,
675 };681 };
...@@ -677,7 +683,7 @@ const MsvcLibDir = struct {...@@ -677,7 +683,7 @@ const MsvcLibDir = struct {
677 // Otherwise, try to get the path from the .dll that would have been683 // Otherwise, try to get the path from the .dll that would have been
678 // loaded via COM for SetupConfiguration.684 // loaded via COM for SetupConfiguration.
679 method2: {685 method2: {
680 return findInstancesDirViaCLSID(allocator) catch |err| switch (err) {686 return findInstancesDirViaCLSID(gpa, io) catch |err| switch (err) {
681 error.OutOfMemory => |e| return e,687 error.OutOfMemory => |e| return e,
682 error.PathNotFound => break :method2,688 error.PathNotFound => break :method2,
683 };689 };
...@@ -685,19 +691,19 @@ const MsvcLibDir = struct {...@@ -685,19 +691,19 @@ const MsvcLibDir = struct {
685 // If that can't be found, fall back to manually appending691 // If that can't be found, fall back to manually appending
686 // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA%692 // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA%
687 method3: {693 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) {
689 error.OutOfMemory => |e| return e,695 error.OutOfMemory => |e| return e,
690 error.InvalidWtf8 => unreachable,696 error.InvalidWtf8 => unreachable,
691 error.EnvironmentVariableNotFound => break :method3,697 error.EnvironmentVariableNotFound => break :method3,
692 };698 };
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" });703 const instances_path = try Dir.path.join(gpa, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" });
698 defer allocator.free(instances_path);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;
701 }707 }
702 return error.PathNotFound;708 return error.PathNotFound;
703 }709 }
...@@ -748,33 +754,33 @@ const MsvcLibDir = struct {...@@ -748,33 +754,33 @@ const MsvcLibDir = struct {
748 ///754 ///
749 /// The logic in this function is intended to match what ISetupConfiguration does755 /// The logic in this function is intended to match what ISetupConfiguration does
750 /// under-the-hood, as verified using Procmon.756 /// 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 {
752 // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances`758 // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances`
753 // This will contain directories with names of instance IDs like 80a758ca,759 // This will contain directories with names of instance IDs like 80a758ca,
754 // which will contain `state.json` files that have the version and760 // which will contain `state.json` files that have the version and
755 // installation directory.761 // installation directory.
756 var instances_dir = try findInstancesDir(allocator);762 var instances_dir = try findInstancesDir(gpa, io);
757 defer instances_dir.close();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;
760 var latest_version_lib_dir: std.ArrayList(u8) = .empty;766 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
763 var latest_version: u64 = 0;769 var latest_version: u64 = 0;
764 var instances_dir_it = instances_dir.iterateAssumeFirstIteration();770 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| {
766 if (entry.kind != .directory) continue;772 if (entry.kind != .directory) continue;
767773
768 var writer: Writer = .fixed(&state_subpath_buf);774 var writer: Writer = .fixed(&state_subpath_buf);
769775
770 writer.writeAll(entry.name) catch unreachable;776 writer.writeAll(entry.name) catch unreachable;
771 writer.writeByte(std.fs.path.sep) catch unreachable;777 writer.writeByte(Dir.path.sep) catch unreachable;
772 writer.writeAll("state.json") catch unreachable;778 writer.writeAll("state.json") catch unreachable;
773779
774 const json_contents = instances_dir.readFileAlloc(writer.buffered(), allocator, .limited(std.math.maxInt(usize))) catch continue;780 const json_contents = instances_dir.readFileAlloc(io, writer.buffered(), gpa, .limited(std.math.maxInt(usize))) catch continue;
775 defer allocator.free(json_contents);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;
778 defer parsed.deinit();784 defer parsed.deinit();
779785
780 if (parsed.value != .object) continue;786 if (parsed.value != .object) continue;
...@@ -791,35 +797,40 @@ const MsvcLibDir = struct {...@@ -791,35 +797,40 @@ const MsvcLibDir = struct {
791 const installation_path = parsed.value.object.get("installationPath") orelse continue;797 const installation_path = parsed.value.object.get("installationPath") orelse continue;
792 if (installation_path != .string) continue;798 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) {
795 error.OutOfMemory => |e| return e,801 error.OutOfMemory => |e| return e,
796 error.PathNotFound => continue,802 error.PathNotFound => continue,
797 };803 };
798 defer allocator.free(lib_dir_path);804 defer gpa.free(lib_dir_path);
799805
800 latest_version_lib_dir.clearRetainingCapacity();806 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);
802 latest_version = parsed_version;808 latest_version = parsed_version;
803 }809 }
804810
805 if (latest_version_lib_dir.items.len == 0) return error.PathNotFound;811 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);
807 }813 }
808814
809 fn libDirFromInstallationPath(allocator: std.mem.Allocator, installation_path: []const u8, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {815 fn libDirFromInstallationPath(
810 var lib_dir_buf = try std.array_list.Managed(u8).initCapacity(allocator, installation_path.len + 64);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);
811 errdefer lib_dir_buf.deinit();822 errdefer lib_dir_buf.deinit();
812823
813 lib_dir_buf.appendSliceAssumeCapacity(installation_path);824 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())) {
816 try lib_dir_buf.append('\\');827 try lib_dir_buf.append('\\');
817 }828 }
818 const installation_path_with_trailing_sep_len = lib_dir_buf.items.len;829 const installation_path_with_trailing_sep_len = lib_dir_buf.items.len;
819830
820 try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt");831 try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt");
821 var default_tools_version_buf: [512]u8 = undefined;832 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 {
823 return error.PathNotFound;834 return error.PathNotFound;
824 };835 };
825 var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n");836 var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n");
...@@ -837,7 +848,7 @@ const MsvcLibDir = struct {...@@ -837,7 +848,7 @@ const MsvcLibDir = struct {
837 else => unreachable,848 else => unreachable,
838 });849 });
839850
840 if (!verifyLibDir(lib_dir_buf.items)) {851 if (!verifyLibDir(io, lib_dir_buf.items)) {
841 return error.PathNotFound;852 return error.PathNotFound;
842 }853 }
843854
...@@ -845,64 +856,64 @@ const MsvcLibDir = struct {...@@ -845,64 +856,64 @@ const MsvcLibDir = struct {
845 }856 }
846857
847 // https://learn.microsoft.com/en-us/visualstudio/install/tools-for-managing-visual-studio-instances?view=vs-2022#editing-the-registry-for-a-visual-studio-instance858 // 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
850 // %localappdata%\Microsoft\VisualStudio\861 // %localappdata%\Microsoft\VisualStudio\
851 // %appdata%\Local\Microsoft\VisualStudio\862 // %appdata%\Local\Microsoft\VisualStudio\
852 const visualstudio_folder_path = std.fs.getAppDataDir(allocator, "Microsoft\\VisualStudio\\") catch return error.PathNotFound;863 const visualstudio_folder_path = std.fs.getAppDataDir(gpa, "Microsoft\\VisualStudio\\") catch return error.PathNotFound;
853 defer allocator.free(visualstudio_folder_path);864 defer gpa.free(visualstudio_folder_path);
854865
855 const vs_versions: []const []const u8 = vs_versions: {866 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;
857 // enumerate folders that contain `privateregistry.bin`, looking for all versions868 // enumerate folders that contain `privateregistry.bin`, looking for all versions
858 // f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\869 // 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, .{
860 .iterate = true,871 .iterate = true,
861 }) catch return error.PathNotFound;872 }) catch return error.PathNotFound;
862 defer visualstudio_folder.close();873 defer visualstudio_folder.close(io);
863874
864 var iterator = visualstudio_folder.iterate();875 var iterator = visualstudio_folder.iterate();
865 break :vs_versions try iterateAndFilterByVersion(&iterator, allocator, "");876 break :vs_versions try iterateAndFilterByVersion(&iterator, gpa, io, "");
866 };877 };
867 defer {878 defer {
868 for (vs_versions) |vs_version| allocator.free(vs_version);879 for (vs_versions) |vs_version| gpa.free(vs_version);
869 allocator.free(vs_versions);880 gpa.free(vs_versions);
870 }881 }
871 var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined;882 var config_subkey_buf: [RegistryWtf16Le.key_name_max_len * 2]u8 = undefined;
872 const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {883 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;884 const privateregistry_absolute_path = Dir.path.join(gpa, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
874 defer allocator.free(privateregistry_absolute_path);885 defer gpa.free(privateregistry_absolute_path);
875 if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue;886 if (!Dir.path.isAbsolute(privateregistry_absolute_path)) continue;
876887
877 const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue;888 const visualstudio_registry = RegistryWtf8.loadFromPath(privateregistry_absolute_path) catch continue;
878 defer visualstudio_registry.closeKey();889 defer visualstudio_registry.closeKey();
879890
880 const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable;891 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) {
883 error.OutOfMemory => return error.OutOfMemory,894 error.OutOfMemory => return error.OutOfMemory,
884 else => continue,895 else => continue,
885 };896 };
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 length897 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
887 allocator.free(source_directories_value);898 gpa.free(source_directories_value);
888 continue;899 continue;
889 }900 }
890901
891 break :source_directories source_directories_value;902 break :source_directories source_directories_value;
892 } else return error.PathNotFound;903 } else return error.PathNotFound;
893 defer allocator.free(source_directories);904 defer gpa.free(source_directories);
894905
895 var source_directories_split = std.mem.splitScalar(u8, source_directories, ';');906 var source_directories_split = std.mem.splitScalar(u8, source_directories, ';');
896907
897 const msvc_dir: []const u8 = msvc_dir: {908 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)) {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)) {
901 allocator.free(msvc_include_dir_maybe_with_trailing_slash);912 gpa.free(msvc_include_dir_maybe_with_trailing_slash);
902 return error.PathNotFound;913 return error.PathNotFound;
903 }914 }
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);
906 errdefer msvc_dir.deinit();917 errdefer msvc_dir.deinit();
907918
908 // String might contain trailing slash, so trim it here919 // String might contain trailing slash, so trim it here
...@@ -924,19 +935,19 @@ const MsvcLibDir = struct {...@@ -924,19 +935,19 @@ const MsvcLibDir = struct {
924 const msvc_dir_with_arch = try msvc_dir.toOwnedSlice();935 const msvc_dir_with_arch = try msvc_dir.toOwnedSlice();
925 break :msvc_dir msvc_dir_with_arch;936 break :msvc_dir msvc_dir_with_arch;
926 };937 };
927 errdefer allocator.free(msvc_dir);938 errdefer gpa.free(msvc_dir);
928939
929 if (!verifyLibDir(msvc_dir)) {940 if (!verifyLibDir(io, msvc_dir)) {
930 return error.PathNotFound;941 return error.PathNotFound;
931 }942 }
932943
933 return msvc_dir;944 return msvc_dir;
934 }945 }
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 {
937 var base_path: std.array_list.Managed(u8) = base_path: {948 var base_path: std.array_list.Managed(u8) = base_path: {
938 try_env: {949 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) {
940 error.OutOfMemory => return error.OutOfMemory,951 error.OutOfMemory => return error.OutOfMemory,
941 else => break :try_env,952 else => break :try_env,
942 };953 };
...@@ -944,8 +955,8 @@ const MsvcLibDir = struct {...@@ -944,8 +955,8 @@ const MsvcLibDir = struct {
944955
945 if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| {956 if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| {
946 if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env;957 if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env;
947 if (!std.fs.path.isAbsolute(VS140COMNTOOLS)) break :try_env;958 if (!Dir.path.isAbsolute(VS140COMNTOOLS)) break :try_env;
948 var list = std.array_list.Managed(u8).init(allocator);959 var list = std.array_list.Managed(u8).init(gpa);
949 errdefer list.deinit();960 errdefer list.deinit();
950961
951 try list.appendSlice(VS140COMNTOOLS); // C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools962 try list.appendSlice(VS140COMNTOOLS); // C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools
...@@ -959,17 +970,17 @@ const MsvcLibDir = struct {...@@ -959,17 +970,17 @@ const MsvcLibDir = struct {
959 const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", .{ .wow64_32 = true }) catch return error.PathNotFound;970 const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", .{ .wow64_32 = true }) catch return error.PathNotFound;
960 defer vs7_key.closeKey();971 defer vs7_key.closeKey();
961 try_vs7_key: {972 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) {
963 error.OutOfMemory => return error.OutOfMemory,974 error.OutOfMemory => return error.OutOfMemory,
964 else => break :try_vs7_key,975 else => break :try_vs7_key,
965 };976 };
966977
967 if (path_maybe_with_trailing_slash.len > std.fs.max_path_bytes or !std.fs.path.isAbsolute(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)) {
968 allocator.free(path_maybe_with_trailing_slash);979 gpa.free(path_maybe_with_trailing_slash);
969 break :try_vs7_key;980 break :try_vs7_key;
970 }981 }
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);
973 errdefer path.deinit();984 errdefer path.deinit();
974985
975 // String might contain trailing slash, so trim it here986 // String might contain trailing slash, so trim it here
...@@ -989,7 +1000,7 @@ const MsvcLibDir = struct {...@@ -989,7 +1000,7 @@ const MsvcLibDir = struct {
989 else => unreachable,1000 else => unreachable,
990 });1001 });
9911002
992 if (!verifyLibDir(base_path.items)) {1003 if (!verifyLibDir(io, base_path.items)) {
993 return error.PathNotFound;1004 return error.PathNotFound;
994 }1005 }
9951006
...@@ -997,13 +1008,13 @@ const MsvcLibDir = struct {...@@ -997,13 +1008,13 @@ const MsvcLibDir = struct {
997 return full_path;1008 return full_path;
998 }1009 }
9991010
1000 fn verifyLibDir(lib_dir_path: []const u8) bool {1011 fn verifyLibDir(io: Io, lib_dir_path: []const u8) bool {
1001 std.debug.assert(std.fs.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*`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;1014 var dir = Dir.openDirAbsolute(io, lib_dir_path, .{}) catch return false;
1004 defer dir.close();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;
1007 if (stat.kind != .file)1018 if (stat.kind != .file)
1008 return false;1019 return false;
10091020
...@@ -1012,18 +1023,18 @@ const MsvcLibDir = struct {...@@ -1012,18 +1023,18 @@ const MsvcLibDir = struct {
10121023
1013 /// Find path to MSVC's `lib/` directory.1024 /// Find path to MSVC's `lib/` directory.
1014 /// Caller owns the result.1025 /// Caller owns the result.
1015 pub fn find(allocator: std.mem.Allocator, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 {1026 pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 {
1016 const full_path = MsvcLibDir.findViaCOM(allocator, arch) catch |err1| switch (err1) {1027 const full_path = MsvcLibDir.findViaCOM(gpa, io, arch) catch |err1| switch (err1) {
1017 error.OutOfMemory => return error.OutOfMemory,1028 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) {
1019 error.OutOfMemory => return error.OutOfMemory,1030 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) {
1021 error.OutOfMemory => return error.OutOfMemory,1032 error.OutOfMemory => return error.OutOfMemory,
1022 error.PathNotFound => return error.MsvcLibDirNotFound,1033 error.PathNotFound => return error.MsvcLibDirNotFound,
1023 },1034 },
1024 },1035 },
1025 };1036 };
1026 errdefer allocator.free(full_path);1037 errdefer gpa.free(full_path);
10271038
1028 return full_path;1039 return full_path;
1029 }1040 }
lib/std/zig/Zir.zig+4-4
...@@ -11,9 +11,11 @@...@@ -11,9 +11,11 @@
11//! * In the future, possibly inline assembly, which needs to get parsed and11//! * In the future, possibly inline assembly, which needs to get parsed and
12//! handled by the codegen backend, and errors reported there. However for now,12//! handled by the codegen backend, and errors reported there. However for now,
13//! inline assembly is not an exception.13//! inline assembly is not an exception.
14const Zir = @This();
15const builtin = @import("builtin");
1416
15const std = @import("std");17const std = @import("std");
16const builtin = @import("builtin");18const Io = std.Io;
17const mem = std.mem;19const mem = std.mem;
18const Allocator = std.mem.Allocator;20const Allocator = std.mem.Allocator;
19const assert = std.debug.assert;21const assert = std.debug.assert;
...@@ -21,8 +23,6 @@ const BigIntConst = std.math.big.int.Const;...@@ -21,8 +23,6 @@ const BigIntConst = std.math.big.int.Const;
21const BigIntMutable = std.math.big.int.Mutable;23const BigIntMutable = std.math.big.int.Mutable;
22const Ast = std.zig.Ast;24const Ast = std.zig.Ast;
2325
24const Zir = @This();
25
26instructions: std.MultiArrayList(Inst).Slice,26instructions: std.MultiArrayList(Inst).Slice,
27/// In order to store references to strings in fewer bytes, we copy all27/// In order to store references to strings in fewer bytes, we copy all
28/// string bytes into here. String bytes can be null. It is up to whomever28/// string bytes into here. String bytes can be null. It is up to whomever
...@@ -45,7 +45,7 @@ pub const Header = extern struct {...@@ -45,7 +45,7 @@ pub const Header = extern struct {
45 /// it's essentially free to have a zero field here and makes the warning go away,45 /// it's essentially free to have a zero field here and makes the warning go away,
46 /// making it more likely that following Valgrind warnings will be taken seriously.46 /// making it more likely that following Valgrind warnings will be taken seriously.
47 unused: u32 = 0,47 unused: u32 = 0,
48 stat_inode: std.fs.File.INode,48 stat_inode: Io.File.INode,
49 stat_size: u64,49 stat_size: u64,
50 stat_mtime: i128,50 stat_mtime: i128,
51};51};
lib/std/zig/Zoir.zig+8-7
...@@ -1,6 +1,13 @@...@@ -1,6 +1,13 @@
1//! Zig Object Intermediate Representation.1//! Zig Object Intermediate Representation.
2//! Simplified AST for the ZON (Zig Object Notation) format.2//! Simplified AST for the ZON (Zig Object Notation) format.
3//! `ZonGen` converts `Ast` to `Zoir`.3//! `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
5nodes: std.MultiArrayList(Node.Repr).Slice,12nodes: std.MultiArrayList(Node.Repr).Slice,
6extra: []u32,13extra: []u32,
...@@ -25,7 +32,7 @@ pub const Header = extern struct {...@@ -25,7 +32,7 @@ pub const Header = extern struct {
25 /// making it more likely that following Valgrind warnings will be taken seriously.32 /// making it more likely that following Valgrind warnings will be taken seriously.
26 unused: u64 = 0,33 unused: u64 = 0,
2734
28 stat_inode: std.fs.File.INode,35 stat_inode: Io.File.INode,
29 stat_size: u64,36 stat_size: u64,
30 stat_mtime: i128,37 stat_mtime: i128,
3138
...@@ -254,9 +261,3 @@ pub const CompileError = extern struct {...@@ -254,9 +261,3 @@ pub const CompileError = extern struct {
254 assert(std.meta.hasUniqueRepresentation(Note));261 assert(std.meta.hasUniqueRepresentation(Note));
255 }262 }
256};263};
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 @@...@@ -1,14 +1,17 @@
1const builtin = @import("builtin");
2const Builder = @This();
3
1const std = @import("../../std.zig");4const std = @import("../../std.zig");
5const Io = std.Io;
2const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
3const assert = std.debug.assert;7const assert = std.debug.assert;
4const bitcode_writer = @import("bitcode_writer.zig");
5const Builder = @This();
6const builtin = @import("builtin");
7const DW = std.dwarf;8const DW = std.dwarf;
8const ir = @import("ir.zig");
9const log = std.log.scoped(.llvm);9const log = std.log.scoped(.llvm);
10const Writer = std.Io.Writer;10const Writer = std.Io.Writer;
1111
12const bitcode_writer = @import("bitcode_writer.zig");
13const ir = @import("ir.zig");
14
12gpa: Allocator,15gpa: Allocator,
13strip: bool,16strip: bool,
1417
...@@ -9573,21 +9576,21 @@ pub fn asmValue(...@@ -9573,21 +9576,21 @@ pub fn asmValue(
9573 return (try self.asmConst(ty, info, assembly, constraints)).toValue();9576 return (try self.asmConst(ty, info, assembly, constraints)).toValue();
9574}9577}
95759578
9576pub fn dump(b: *Builder) void {9579pub fn dump(b: *Builder, io: Io) void {
9577 var buffer: [4000]u8 = undefined;9580 var buffer: [4000]u8 = undefined;
9578 const stderr: std.fs.File = .stderr();9581 const stderr: Io.File = .stderr();
9579 b.printToFile(stderr, &buffer) catch {};9582 b.printToFile(io, stderr, &buffer) catch {};
9580}9583}
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 {
9583 var buffer: [4000]u8 = undefined;9586 var buffer: [4000]u8 = undefined;
9584 const file = try dir.createFile(path, .{});9587 const file = try dir.createFile(io, path, .{});
9585 defer file.close();9588 defer file.close(io);
9586 try b.printToFile(file, &buffer);9589 try b.printToFile(io, file, &buffer);
9587}9590}
95889591
9589pub fn printToFile(b: *Builder, file: std.fs.File, buffer: []u8) !void {9592pub fn printToFile(b: *Builder, io: Io, file: Io.File, buffer: []u8) !void {
9590 var fw = file.writer(buffer);9593 var fw = file.writer(io, buffer);
9591 try print(b, &fw.interface);9594 try print(b, &fw.interface);
9592 try fw.interface.flush();9595 try fw.interface.flush();
9593}9596}
lib/std/zig/parser_test.zig+27-18
...@@ -1,7 +1,6 @@...@@ -1,7 +1,6 @@
1const std = @import("std");1const std = @import("std");
2const mem = std.mem;2const Io = std.Io;
3const print = std.debug.print;3const Allocator = std.mem.Allocator;
4const maxInt = std.math.maxInt;
54
6test "zig fmt: remove extra whitespace at start and end of file with comment between" {5test "zig fmt: remove extra whitespace at start and end of file with comment between" {
7 try testTransform(6 try testTransform(
...@@ -4539,7 +4538,7 @@ test "zig fmt: Only indent multiline string literals in function calls" {...@@ -4539,7 +4538,7 @@ test "zig fmt: Only indent multiline string literals in function calls" {
4539test "zig fmt: Don't add extra newline after if" {4538test "zig fmt: Don't add extra newline after if" {
4540 try testCanonical(4539 try testCanonical(
4541 \\pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {4540 \\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, .{})) {
4543 \\ return;4542 \\ return;
4544 \\ }4543 \\ }
4545 \\}4544 \\}
...@@ -6332,54 +6331,64 @@ test "ampersand" {...@@ -6332,54 +6331,64 @@ test "ampersand" {
63326331
6333var fixed_buffer_mem: [100 * 1024]u8 = undefined;6332var 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 {
6336 var buffer: [64]u8 = undefined;6335 var buffer: [64]u8 = undefined;
6337 const stderr, _ = std.debug.lockStderrWriter(&buffer);6336 const stderr = try io.lockStderr(&buffer, null);
6338 defer std.debug.unlockStderrWriter();6337 defer io.unlockStderr();
6338 const writer = &stderr.file_writer.interface;
63396339
6340 var tree = try std.zig.Ast.parse(allocator, source, .zig);6340 var tree = try std.zig.Ast.parse(allocator, source, .zig);
6341 defer tree.deinit(allocator);6341 defer tree.deinit(allocator);
63426342
6343 for (tree.errors) |parse_error| {6343 for (tree.errors) |parse_error| {
6344 const loc = tree.tokenLocation(0, parse_error.token);6344 const loc = tree.tokenLocation(0, parse_error.token);
6345 try stderr.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 });6345 try writer.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 });
6346 try tree.renderError(parse_error, stderr);6346 try tree.renderError(parse_error, writer);
6347 try stderr.print("\n{s}\n", .{source[loc.line_start..loc.line_end]});6347 try writer.print("\n{s}\n", .{source[loc.line_start..loc.line_end]});
6348 {6348 {
6349 var i: usize = 0;6349 var i: usize = 0;
6350 while (i < loc.column) : (i += 1) {6350 while (i < loc.column) : (i += 1) {
6351 try stderr.writeAll(" ");6351 try writer.writeAll(" ");
6352 }6352 }
6353 try stderr.writeAll("^");6353 try writer.writeAll("^");
6354 }6354 }
6355 try stderr.writeAll("\n");6355 try writer.writeAll("\n");
6356 }6356 }
6357 if (tree.errors.len != 0) {6357 if (tree.errors.len != 0) {
6358 return error.ParseError;6358 return error.ParseError;
6359 }6359 }
63606360
6361 const formatted = try tree.renderAlloc(allocator);6361 const formatted = try tree.renderAlloc(allocator);
6362 anything_changed.* = !mem.eql(u8, formatted, source);6362 anything_changed.* = !std.mem.eql(u8, formatted, source);
6363 return formatted;6363 return formatted;
6364}6364}
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 {
6366 // reset the fixed buffer allocator each run so that it can be re-used for each6372 // reset the fixed buffer allocator each run so that it can be re-used for each
6367 // iteration of the failing index6373 // iteration of the failing index
6368 fba.reset();6374 fba.reset();
6369 var anything_changed: bool = undefined;6375 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);
6371 try std.testing.expectEqualStrings(expected_source, result_source);6377 try std.testing.expectEqualStrings(expected_source, result_source);
6372 const changes_expected = source.ptr != expected_source.ptr;6378 const changes_expected = source.ptr != expected_source.ptr;
6373 if (anything_changed != changes_expected) {6379 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 });
6375 return error.TestFailed;6381 return error.TestFailed;
6376 }6382 }
6377 try std.testing.expect(anything_changed == changes_expected);6383 try std.testing.expect(anything_changed == changes_expected);
6378 allocator.free(result_source);6384 allocator.free(result_source);
6379}6385}
6380fn testTransform(source: [:0]const u8, expected_source: []const u8) !void {6386fn testTransform(source: [:0]const u8, expected_source: []const u8) !void {
6387 const io = std.testing.io;
6381 var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);6388 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 });
6383}6392}
6384fn testCanonical(source: [:0]const u8) !void {6393fn testCanonical(source: [:0]const u8) !void {
6385 return testTransform(source, source);6394 return testTransform(source, source);
lib/std/zig/perf_test.zig+2-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const mem = std.mem;3const mem = std.mem;
3const Tokenizer = std.zig.Tokenizer;4const Tokenizer = std.zig.Tokenizer;
4const fmtIntSizeBin = std.fmt.fmtIntSizeBin;5const fmtIntSizeBin = std.fmt.fmtIntSizeBin;
...@@ -22,7 +23,7 @@ pub fn main() !void {...@@ -22,7 +23,7 @@ pub fn main() !void {
22 const bytes_per_sec = @as(u64, @intFromFloat(@floor(bytes_per_sec_float)));23 const bytes_per_sec = @as(u64, @intFromFloat(@floor(bytes_per_sec_float)));
2324
24 var stdout_buffer: [1024]u8 = undefined;25 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);
26 const stdout = &stdout_writer.interface;27 const stdout = &stdout_writer.interface;
27 try stdout.print("parsing speed: {Bi:.2}/s, {Bi:.2} used \n", .{ bytes_per_sec, memory_used });28 try stdout.print("parsing speed: {Bi:.2}/s, {Bi:.2} used \n", .{ bytes_per_sec, memory_used });
28 try stdout.flush();29 try stdout.flush();
lib/std/zig/system.zig+23-21
...@@ -1,11 +1,12 @@...@@ -1,11 +1,12 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
2const std = @import("../std.zig");4const std = @import("../std.zig");
3const mem = std.mem;5const mem = std.mem;
4const elf = std.elf;6const elf = std.elf;
5const fs = std.fs;7const fs = std.fs;
6const assert = std.debug.assert;8const assert = std.debug.assert;
7const Target = std.Target;9const Target = std.Target;
8const native_endian = builtin.cpu.arch.endian();
9const posix = std.posix;10const posix = std.posix;
10const Io = std.Io;11const Io = std.Io;
1112
...@@ -39,6 +40,7 @@ pub const GetExternalExecutorOptions = struct {...@@ -39,6 +40,7 @@ pub const GetExternalExecutorOptions = struct {
39/// Return whether or not the given host is capable of running executables of40/// Return whether or not the given host is capable of running executables of
40/// the other target.41/// the other target.
41pub fn getExternalExecutor(42pub fn getExternalExecutor(
43 io: Io,
42 host: *const std.Target,44 host: *const std.Target,
43 candidate: *const std.Target,45 candidate: *const std.Target,
44 options: GetExternalExecutorOptions,46 options: GetExternalExecutorOptions,
...@@ -69,7 +71,7 @@ pub fn getExternalExecutor(...@@ -69,7 +71,7 @@ pub fn getExternalExecutor(
69 if (os_match and cpu_ok) native: {71 if (os_match and cpu_ok) native: {
70 if (options.link_libc) {72 if (options.link_libc) {
71 if (candidate.dynamic_linker.get()) |candidate_dl| {73 if (candidate.dynamic_linker.get()) |candidate_dl| {
72 fs.cwd().access(candidate_dl, .{}) catch {74 Io.Dir.cwd().access(io, candidate_dl, .{}) catch {
73 bad_result = .{ .bad_dl = candidate_dl };75 bad_result = .{ .bad_dl = candidate_dl };
74 break :native;76 break :native;
75 };77 };
...@@ -209,7 +211,6 @@ pub const DetectError = error{...@@ -209,7 +211,6 @@ pub const DetectError = error{
209 DeviceBusy,211 DeviceBusy,
210 OSVersionDetectionFail,212 OSVersionDetectionFail,
211 Unexpected,213 Unexpected,
212 ProcessNotFound,
213} || Io.Cancelable;214} || Io.Cancelable;
214215
215/// Given a `Target.Query`, which specifies in detail which parts of the216/// 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 {...@@ -247,7 +248,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
247 os.version_range.windows.min = detected_version;248 os.version_range.windows.min = detected_version;
248 os.version_range.windows.max = detected_version;249 os.version_range.windows.max = detected_version;
249 },250 },
250 .macos => try darwin.macos.detect(&os),251 .macos => try darwin.macos.detect(io, &os),
251 .freebsd, .netbsd, .dragonfly => {252 .freebsd, .netbsd, .dragonfly => {
252 const key = switch (builtin.target.os.tag) {253 const key = switch (builtin.target.os.tag) {
253 .freebsd => "kern.osreldate",254 .freebsd => "kern.osreldate",
...@@ -322,7 +323,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {...@@ -322,7 +323,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
322 error.Unexpected => return error.OSVersionDetectionFail,323 error.Unexpected => return error.OSVersionDetectionFail,
323 };324 };
324325
325 if (Target.Query.parseVersion(buf[0..len :0])) |ver| {326 if (Target.Query.parseVersion(buf[0 .. len - 1 :0])) |ver| {
326 assert(ver.build == null);327 assert(ver.build == null);
327 assert(ver.pre == null);328 assert(ver.pre == null);
328 os.version_range.semver.min = ver;329 os.version_range.semver.min = ver;
...@@ -422,7 +423,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {...@@ -422,7 +423,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
422 error.SocketUnconnected => return error.Unexpected,423 error.SocketUnconnected => return error.Unexpected,
423424
424 error.AccessDenied,425 error.AccessDenied,
425 error.ProcessNotFound,
426 error.SymLinkLoop,426 error.SymLinkLoop,
427 error.ProcessFdQuotaExceeded,427 error.ProcessFdQuotaExceeded,
428 error.SystemFdQuotaExceeded,428 error.SystemFdQuotaExceeded,
...@@ -553,7 +553,6 @@ pub const AbiAndDynamicLinkerFromFileError = error{...@@ -553,7 +553,6 @@ pub const AbiAndDynamicLinkerFromFileError = error{
553 SystemResources,553 SystemResources,
554 ProcessFdQuotaExceeded,554 ProcessFdQuotaExceeded,
555 SystemFdQuotaExceeded,555 SystemFdQuotaExceeded,
556 ProcessNotFound,
557 IsDir,556 IsDir,
558 WouldBlock,557 WouldBlock,
559 InputOutput,558 InputOutput,
...@@ -693,8 +692,10 @@ fn abiAndDynamicLinkerFromFile(...@@ -693,8 +692,10 @@ fn abiAndDynamicLinkerFromFile(
693692
694 // So far, no luck. Next we try to see if the information is693 // So far, no luck. Next we try to see if the information is
695 // present in the symlink data for the dynamic linker path.694 // present in the symlink data for the dynamic linker path.
696 var link_buf: [posix.PATH_MAX]u8 = undefined;695 var link_buffer: [posix.PATH_MAX]u8 = undefined;
697 const link_name = posix.readlink(dl_path, &link_buf) catch |err| switch (err) {696 const link_name = if (Io.Dir.readLinkAbsolute(io, dl_path, &link_buffer)) |n|
697 link_buffer[0..n]
698 else |err| switch (err) {
698 error.NameTooLong => unreachable,699 error.NameTooLong => unreachable,
699 error.BadPathName => unreachable, // Windows only700 error.BadPathName => unreachable, // Windows only
700 error.UnsupportedReparsePointType => unreachable, // Windows only701 error.UnsupportedReparsePointType => unreachable, // Windows only
...@@ -711,6 +712,7 @@ fn abiAndDynamicLinkerFromFile(...@@ -711,6 +712,7 @@ fn abiAndDynamicLinkerFromFile(
711 error.SystemResources,712 error.SystemResources,
712 error.FileSystem,713 error.FileSystem,
713 error.SymLinkLoop,714 error.SymLinkLoop,
715 error.Canceled,
714 error.Unexpected,716 error.Unexpected,
715 => |e| return e,717 => |e| return e,
716 };718 };
...@@ -786,7 +788,9 @@ test glibcVerFromLinkName {...@@ -786,7 +788,9 @@ test glibcVerFromLinkName {
786}788}
787789
788fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {790fn 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) {
790 error.NameTooLong => return error.Unexpected,794 error.NameTooLong => return error.Unexpected,
791 error.BadPathName => return error.Unexpected,795 error.BadPathName => return error.Unexpected,
792 error.DeviceBusy => return error.Unexpected,796 error.DeviceBusy => return error.Unexpected,
...@@ -805,7 +809,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {...@@ -805,7 +809,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
805 error.Unexpected => |e| return e,809 error.Unexpected => |e| return e,
806 error.Canceled => |e| return e,810 error.Canceled => |e| return e,
807 };811 };
808 defer dir.close();812 defer dir.close(io);
809813
810 // Now we have a candidate for the path to libc shared object. In814 // Now we have a candidate for the path to libc shared object. In
811 // the past, we used readlink() here because the link name would815 // the past, we used readlink() here because the link name would
...@@ -815,14 +819,14 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {...@@ -815,14 +819,14 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
815 // .dynstr section, and finding the max version number of symbols819 // .dynstr section, and finding the max version number of symbols
816 // that start with "GLIBC_2.".820 // that start with "GLIBC_2.".
817 const glibc_so_basename = "libc.so.6";821 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) {
819 error.NameTooLong => return error.Unexpected,823 error.NameTooLong => return error.Unexpected,
820 error.BadPathName => return error.Unexpected,824 error.BadPathName => return error.Unexpected,
821 error.PipeBusy => return error.Unexpected, // Windows-only825 error.PipeBusy => return error.Unexpected, // Windows-only
822 error.SharingViolation => return error.Unexpected, // Windows-only826 error.SharingViolation => return error.Unexpected, // Windows-only
823 error.NetworkNotFound => return error.Unexpected, // Windows-only827 error.NetworkNotFound => return error.Unexpected, // Windows-only
824 error.AntivirusInterference => return error.Unexpected, // Windows-only828 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.
826 error.NoSpaceLeft => return error.Unexpected, // read-only830 error.NoSpaceLeft => return error.Unexpected, // read-only
827 error.PathAlreadyExists => return error.Unexpected, // read-only831 error.PathAlreadyExists => return error.Unexpected, // read-only
828 error.DeviceBusy => return error.Unexpected, // read-only832 error.DeviceBusy => return error.Unexpected, // read-only
...@@ -837,7 +841,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {...@@ -837,7 +841,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
837 error.NotDir => return error.GLibCNotFound,841 error.NotDir => return error.GLibCNotFound,
838 error.IsDir => return error.GLibCNotFound,842 error.IsDir => return error.GLibCNotFound,
839843
840 error.ProcessNotFound => |e| return e,
841 error.ProcessFdQuotaExceeded => |e| return e,844 error.ProcessFdQuotaExceeded => |e| return e,
842 error.SystemFdQuotaExceeded => |e| return e,845 error.SystemFdQuotaExceeded => |e| return e,
843 error.SystemResources => |e| return e,846 error.SystemResources => |e| return e,
...@@ -845,11 +848,11 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {...@@ -845,11 +848,11 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
845 error.Unexpected => |e| return e,848 error.Unexpected => |e| return e,
846 error.Canceled => |e| return e,849 error.Canceled => |e| return e,
847 };850 };
848 defer file.close();851 defer file.close(io);
849852
850 // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.853 // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
851 var buffer: [8000]u8 = undefined;854 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
854 return glibcVerFromSoFile(&file_reader) catch |err| switch (err) {857 return glibcVerFromSoFile(&file_reader) catch |err| switch (err) {
855 error.InvalidElfMagic,858 error.InvalidElfMagic,
...@@ -1024,14 +1027,14 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ...@@ -1024,14 +1027,14 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
1024 };1027 };
10251028
1026 while (true) {1029 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) {
1028 error.NoSpaceLeft => return error.Unexpected,1031 error.NoSpaceLeft => return error.Unexpected,
1029 error.NameTooLong => return error.Unexpected,1032 error.NameTooLong => return error.Unexpected,
1030 error.PathAlreadyExists => return error.Unexpected,1033 error.PathAlreadyExists => return error.Unexpected,
1031 error.SharingViolation => return error.Unexpected,1034 error.SharingViolation => return error.Unexpected,
1032 error.BadPathName => return error.Unexpected,1035 error.BadPathName => return error.Unexpected,
1033 error.PipeBusy => return error.Unexpected,1036 error.PipeBusy => return error.Unexpected,
1034 error.FileLocksNotSupported => return error.Unexpected,1037 error.FileLocksUnsupported => return error.Unexpected,
1035 error.FileBusy => return error.Unexpected, // opened without write permissions1038 error.FileBusy => return error.Unexpected, // opened without write permissions
1036 error.AntivirusInterference => return error.Unexpected, // Windows-only error1039 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...@@ -1049,9 +1052,9 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
1049 else => |e| return e,1052 else => |e| return e,
1050 };1053 };
1051 var is_elf_file = false;1054 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);
1055 file_name = undefined; // it aliases file_reader_buffer1058 file_name = undefined; // it aliases file_reader_buffer
10561059
1057 const header = elf.Header.read(&file_reader.interface) catch |hdr_err| switch (hdr_err) {1060 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...@@ -1101,7 +1104,6 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
1101 error.SymLinkLoop,1104 error.SymLinkLoop,
1102 error.ProcessFdQuotaExceeded,1105 error.ProcessFdQuotaExceeded,
1103 error.SystemFdQuotaExceeded,1106 error.SystemFdQuotaExceeded,
1104 error.ProcessNotFound,
1105 error.Canceled,1107 error.Canceled,
1106 => |e| return e,1108 => |e| return e,
11071109
lib/std/zig/system/NativePaths.zig+7-6
...@@ -1,11 +1,12 @@...@@ -1,11 +1,12 @@
1const std = @import("../../std.zig");1const NativePaths = @This();
2const builtin = @import("builtin");2const builtin = @import("builtin");
3
4const std = @import("../../std.zig");
5const Io = std.Io;
3const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
4const process = std.process;7const process = std.process;
5const mem = std.mem;8const mem = std.mem;
69
7const NativePaths = @This();
8
9arena: Allocator,10arena: Allocator,
10include_dirs: std.ArrayList([]const u8) = .empty,11include_dirs: std.ArrayList([]const u8) = .empty,
11lib_dirs: std.ArrayList([]const u8) = .empty,12lib_dirs: std.ArrayList([]const u8) = .empty,
...@@ -13,7 +14,7 @@ framework_dirs: std.ArrayList([]const u8) = .empty,...@@ -13,7 +14,7 @@ framework_dirs: std.ArrayList([]const u8) = .empty,
13rpaths: std.ArrayList([]const u8) = .empty,14rpaths: std.ArrayList([]const u8) = .empty,
14warnings: std.ArrayList([]const u8) = .empty,15warnings: 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 {
17 var self: NativePaths = .{ .arena = arena };18 var self: NativePaths = .{ .arena = arena };
18 var is_nix = false;19 var is_nix = false;
19 if (process.getEnvVarOwned(arena, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {20 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 {...@@ -115,8 +116,8 @@ pub fn detect(arena: Allocator, native_target: *const std.Target) !NativePaths {
115116
116 // TODO: consider also adding macports paths117 // TODO: consider also adding macports paths
117 if (builtin.target.os.tag.isDarwin()) {118 if (builtin.target.os.tag.isDarwin()) {
118 if (std.zig.system.darwin.isSdkInstalled(arena)) sdk: {119 if (std.zig.system.darwin.isSdkInstalled(arena, io)) sdk: {
119 const sdk = std.zig.system.darwin.getSdk(arena, native_target) orelse break :sdk;120 const sdk = std.zig.system.darwin.getSdk(arena, io, native_target) orelse break :sdk;
120 try self.addLibDir(try std.fs.path.join(arena, &.{ sdk, "usr/lib" }));121 try self.addLibDir(try std.fs.path.join(arena, &.{ sdk, "usr/lib" }));
121 try self.addFrameworkDir(try std.fs.path.join(arena, &.{ sdk, "System/Library/Frameworks" }));122 try self.addFrameworkDir(try std.fs.path.join(arena, &.{ sdk, "System/Library/Frameworks" }));
122 try self.addIncludeDir(try std.fs.path.join(arena, &.{ sdk, "usr/include" }));123 try self.addIncludeDir(try std.fs.path.join(arena, &.{ sdk, "usr/include" }));
lib/std/zig/system/darwin.zig+15-14
...@@ -1,28 +1,29 @@...@@ -1,28 +1,29 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const mem = std.mem;3const mem = std.mem;
3const Allocator = mem.Allocator;4const Allocator = std.mem.Allocator;
4const Target = std.Target;5const Target = std.Target;
5const Version = std.SemanticVersion;6const Version = std.SemanticVersion;
67
7pub const macos = @import("darwin/macos.zig");8pub const macos = @import("darwin/macos.zig");
89
9/// Check if SDK is installed on Darwin without triggering CLT installation popup window.10/// 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.
11/// Therefore, we resort to invoking `xcode-select --print-path` and checking13/// Therefore, we resort to invoking `xcode-select --print-path` and checking
12/// if the status is nonzero.14/// if the status is nonzero.
15///
13/// stderr from xcode-select is ignored.16/// stderr from xcode-select is ignored.
17///
14/// If error.OutOfMemory occurs in Allocator, this function returns null.18/// If error.OutOfMemory occurs in Allocator, this function returns null.
15pub fn isSdkInstalled(allocator: Allocator) bool {19pub fn isSdkInstalled(gpa: Allocator, io: Io) bool {
16 const result = std.process.Child.run(.{20 const result = std.process.Child.run(gpa, io, .{
17 .allocator = allocator,
18 .argv = &.{ "xcode-select", "--print-path" },21 .argv = &.{ "xcode-select", "--print-path" },
19 }) catch return false;22 }) catch return false;
20
21 defer {23 defer {
22 allocator.free(result.stderr);24 gpa.free(result.stderr);
23 allocator.free(result.stdout);25 gpa.free(result.stdout);
24 }26 }
25
26 return switch (result.term) {27 return switch (result.term) {
27 .Exited => |code| if (code == 0) result.stdout.len > 0 else false,28 .Exited => |code| if (code == 0) result.stdout.len > 0 else false,
28 else => false,29 else => false,
...@@ -34,7 +35,7 @@ pub fn isSdkInstalled(allocator: Allocator) bool {...@@ -34,7 +35,7 @@ pub fn isSdkInstalled(allocator: Allocator) bool {
34/// Caller owns the memory.35/// Caller owns the memory.
35/// stderr from xcrun is ignored.36/// stderr from xcrun is ignored.
36/// If error.OutOfMemory occurs in Allocator, this function returns null.37/// 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 {
38 const is_simulator_abi = target.abi == .simulator;39 const is_simulator_abi = target.abi == .simulator;
39 const sdk = switch (target.os.tag) {40 const sdk = switch (target.os.tag) {
40 .driverkit => "driverkit",41 .driverkit => "driverkit",
...@@ -46,16 +47,16 @@ pub fn getSdk(allocator: Allocator, target: *const Target) ?[]const u8 {...@@ -46,16 +47,16 @@ pub fn getSdk(allocator: Allocator, target: *const Target) ?[]const u8 {
46 else => return null,47 else => return null,
47 };48 };
48 const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" };49 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;
50 defer {51 defer {
51 allocator.free(result.stderr);52 gpa.free(result.stderr);
52 allocator.free(result.stdout);53 gpa.free(result.stdout);
53 }54 }
54 switch (result.term) {55 switch (result.term) {
55 .Exited => |code| if (code != 0) return null,56 .Exited => |code| if (code != 0) return null,
56 else => return null,57 else => return null,
57 }58 }
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;
59}60}
6061
61test {62test {
lib/std/zig/system/darwin/macos.zig+5-4
...@@ -1,14 +1,15 @@...@@ -1,14 +1,15 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
3const assert = std.debug.assert;5const assert = std.debug.assert;
4const mem = std.mem;6const mem = std.mem;
5const testing = std.testing;7const testing = std.testing;
6
7const Target = std.Target;8const Target = std.Target;
89
9/// Detect macOS version.10/// Detect macOS version.
10/// `target_os` is not modified in case of error.11/// `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 {
12 // Drop use of osproductversion sysctl because:13 // Drop use of osproductversion sysctl because:
13 // 1. only available 10.13.4 High Sierra and later14 // 1. only available 10.13.4 High Sierra and later
14 // 2. when used from a binary built against < SDK 11.0 it returns 10.16 and masks Big Sur 11.x version15 // 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 {...@@ -54,7 +55,7 @@ pub fn detect(target_os: *Target.Os) !void {
54 // approx. 4 times historical file size55 // approx. 4 times historical file size
55 var buf: [2048]u8 = undefined;56 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| {
58 if (parseSystemVersion(bytes)) |ver| {59 if (parseSystemVersion(bytes)) |ver| {
59 // never return non-canonical `10.(16+)`60 // never return non-canonical `10.(16+)`
60 if (!(ver.major == 10 and ver.minor >= 16)) {61 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 {...@@ -444,10 +444,10 @@ inline fn getAArch64CpuFeature(comptime feat_reg: []const u8) u64 {
444}444}
445445
446pub fn detectNativeCpuAndFeatures(io: Io) ?Target.Cpu {446pub 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) {
448 else => return null,448 else => return null,
449 };449 };
450 defer file.close();450 defer file.close(io);
451451
452 var buffer: [4096]u8 = undefined; // "flags" lines can get pretty long.452 var buffer: [4096]u8 = undefined; // "flags" lines can get pretty long.
453 var file_reader = file.reader(io, &buffer);453 var file_reader = file.reader(io, &buffer);
lib/std/zip.zig+16-12
...@@ -4,9 +4,11 @@...@@ -4,9 +4,11 @@
4//! Note that this file uses the abbreviation "cd" for "central directory"4//! Note that this file uses the abbreviation "cd" for "central directory"
55
6const builtin = @import("builtin");6const builtin = @import("builtin");
7const std = @import("std");
8const File = std.fs.File;
9const is_le = builtin.target.cpu.arch.endian() == .little;7const is_le = builtin.target.cpu.arch.endian() == .little;
8
9const std = @import("std");
10const Io = std.Io;
11const File = std.Io.File;
10const Writer = std.Io.Writer;12const Writer = std.Io.Writer;
11const Reader = std.Io.Reader;13const Reader = std.Io.Reader;
12const flate = std.compress.flate;14const flate = std.compress.flate;
...@@ -115,7 +117,7 @@ pub const EndRecord = extern struct {...@@ -115,7 +117,7 @@ pub const EndRecord = extern struct {
115 return record;117 return record;
116 }118 }
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{
119 ZipNoEndRecord,121 ZipNoEndRecord,
120 EndOfStream,122 EndOfStream,
121 ReadFailed,123 ReadFailed,
...@@ -460,8 +462,10 @@ pub const Iterator = struct {...@@ -460,8 +462,10 @@ pub const Iterator = struct {
460 stream: *File.Reader,462 stream: *File.Reader,
461 options: ExtractOptions,463 options: ExtractOptions,
462 filename_buf: []u8,464 filename_buf: []u8,
463 dest: std.fs.Dir,465 dest: Io.Dir,
464 ) !void {466 ) !void {
467 const io = stream.io;
468
465 if (filename_buf.len < self.filename_len)469 if (filename_buf.len < self.filename_len)
466 return error.ZipInsufficientBuffer;470 return error.ZipInsufficientBuffer;
467 switch (self.compression_method) {471 switch (self.compression_method) {
...@@ -550,23 +554,23 @@ pub const Iterator = struct {...@@ -550,23 +554,23 @@ pub const Iterator = struct {
550 if (filename[filename.len - 1] == '/') {554 if (filename[filename.len - 1] == '/') {
551 if (self.uncompressed_size != 0)555 if (self.uncompressed_size != 0)
552 return error.ZipBadDirectorySize;556 return error.ZipBadDirectorySize;
553 try dest.makePath(filename[0 .. filename.len - 1]);557 try dest.createDirPath(io, filename[0 .. filename.len - 1]);
554 return;558 return;
555 }559 }
556560
557 const out_file = blk: {561 const out_file = blk: {
558 if (std.fs.path.dirname(filename)) |dirname| {562 if (std.fs.path.dirname(filename)) |dirname| {
559 var parent_dir = try dest.makeOpenPath(dirname, .{});563 var parent_dir = try dest.createDirPathOpen(io, dirname, .{});
560 defer parent_dir.close();564 defer parent_dir.close(io);
561565
562 const basename = std.fs.path.basename(filename);566 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 });
564 }568 }
565 break :blk try dest.createFile(filename, .{ .exclusive = true });569 break :blk try dest.createFile(io, filename, .{ .exclusive = true });
566 };570 };
567 defer out_file.close();571 defer out_file.close(io);
568 var out_file_buffer: [1024]u8 = undefined;572 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);
570 const local_data_file_offset: u64 =574 const local_data_file_offset: u64 =
571 @as(u64, self.file_offset) +575 @as(u64, self.file_offset) +
572 @as(u64, @sizeOf(LocalFileHeader)) +576 @as(u64, @sizeOf(LocalFileHeader)) +
...@@ -647,7 +651,7 @@ pub const ExtractOptions = struct {...@@ -647,7 +651,7 @@ pub const ExtractOptions = struct {
647};651};
648652
649/// Extract the zipped files to the given `dest` directory.653/// 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 {
651 if (options.verify_checksums) @panic("TODO unimplemented");655 if (options.verify_checksums) @panic("TODO unimplemented");
652656
653 var iter = try Iterator.init(fr);657 var iter = try Iterator.init(fr);
src/Air/print.zig+18-10
...@@ -9,7 +9,7 @@ const Type = @import("../Type.zig");...@@ -9,7 +9,7 @@ const Type = @import("../Type.zig");
9const Air = @import("../Air.zig");9const Air = @import("../Air.zig");
10const InternPool = @import("../InternPool.zig");10const 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 {
13 comptime assert(build_options.enable_debug_extensions);13 comptime assert(build_options.enable_debug_extensions);
14 const instruction_bytes = air.instructions.len *14 const instruction_bytes = air.instructions.len *
15 // Here we don't use @sizeOf(Air.Inst.Data) because it would include15 // 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...@@ -24,7 +24,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air
24 liveness_special_bytes + tomb_bytes;24 liveness_special_bytes + tomb_bytes;
2525
26 // zig fmt: off26 // zig fmt: off
27 stream.print(27 try stream.print(
28 \\# Total AIR+Liveness bytes: {Bi}28 \\# Total AIR+Liveness bytes: {Bi}
29 \\# AIR Instructions: {d} ({Bi})29 \\# AIR Instructions: {d} ({Bi})
30 \\# AIR Extra Data: {d} ({Bi})30 \\# AIR Extra Data: {d} ({Bi})
...@@ -39,7 +39,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air...@@ -39,7 +39,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air
39 tomb_bytes,39 tomb_bytes,
40 if (liveness) |l| l.extra.len else 0, liveness_extra_bytes,40 if (liveness) |l| l.extra.len else 0, liveness_extra_bytes,
41 if (liveness) |l| l.special.count() else 0, liveness_special_bytes,41 if (liveness) |l| l.special.count() else 0, liveness_special_bytes,
42 }) catch return;42 });
43 // zig fmt: on43 // zig fmt: on
4444
45 var writer: Writer = .{45 var writer: Writer = .{
...@@ -50,7 +50,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air...@@ -50,7 +50,7 @@ pub fn write(air: Air, stream: *std.Io.Writer, pt: Zcu.PerThread, liveness: ?Air
50 .indent = 2,50 .indent = 2,
51 .skip_body = false,51 .skip_body = false,
52 };52 };
53 writer.writeBody(stream, air.getMainBody()) catch return;53 try writer.writeBody(stream, air.getMainBody());
54}54}
5555
56pub fn writeInst(56pub fn writeInst(
...@@ -73,15 +73,23 @@ pub fn writeInst(...@@ -73,15 +73,23 @@ pub fn writeInst(
73}73}
7474
75pub fn dump(air: Air, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {75pub fn dump(air: Air, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
76 const stderr_bw, _ = std.debug.lockStderrWriter(&.{});76 const comp = pt.zcu.comp;
77 defer std.debug.unlockStderrWriter();77 const io = comp.io;
78 air.write(stderr_bw, pt, liveness);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);
79}83}
8084
81pub fn dumpInst(air: Air, inst: Air.Inst.Index, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {85pub fn dumpInst(air: Air, inst: Air.Inst.Index, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
82 const stderr_bw, _ = std.debug.lockStderrWriter(&.{});86 const comp = pt.zcu.comp;
83 defer std.debug.unlockStderrWriter();87 const io = comp.io;
84 air.writeInst(stderr_bw, inst, pt, liveness);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);
85}93}
8694
87const Writer = struct {95const Writer = struct {
src/Builtin.zig+3-2
...@@ -313,8 +313,9 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {...@@ -313,8 +313,9 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {
313 assert(file.source != null);313 assert(file.source != null);
314314
315 const root_dir, const sub_path = file.path.openInfo(comp.dirs);315 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| {
318 if (stat.size != file.source.?.len) {319 if (stat.size != file.source.?.len) {
319 std.log.warn(320 std.log.warn(
320 "the cached file '{f}' had the wrong size. Expected {d}, found {d}. " ++321 "the cached file '{f}' had the wrong size. Expected {d}, found {d}. " ++
...@@ -342,7 +343,7 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {...@@ -342,7 +343,7 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {
342 }343 }
343344
344 // `make_path` matters because the dir hasn't actually been created yet.345 // `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 = &.{} });
346 defer af.deinit();347 defer af.deinit();
347 try af.file_writer.interface.writeAll(file.source.?);348 try af.file_writer.interface.writeAll(file.source.?);
348 af.finish() catch |err| switch (err) {349 af.finish() catch |err| switch (err) {
src/Compilation.zig+230-206
...@@ -446,11 +446,11 @@ pub const Path = struct {...@@ -446,11 +446,11 @@ pub const Path = struct {
446 }446 }
447447
448 /// Given a `Path`, returns the directory handle and sub path to be used to open the path.448 /// 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 } {
450 const dir = switch (p.root) {450 const dir = switch (p.root) {
451 .none => {451 .none => {
452 const cwd_sub_path = absToCwdRelative(p.sub_path, dirs.cwd);452 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 };
454 },454 },
455 .zig_lib => dirs.zig_lib.handle,455 .zig_lib => dirs.zig_lib.handle,
456 .global_cache => dirs.global_cache.handle,456 .global_cache => dirs.global_cache.handle,
...@@ -721,13 +721,13 @@ pub const Directories = struct {...@@ -721,13 +721,13 @@ pub const Directories = struct {
721 /// This may be the same as `global_cache`.721 /// This may be the same as `global_cache`.
722 local_cache: Cache.Directory,722 local_cache: Cache.Directory,
723723
724 pub fn deinit(dirs: *Directories) void {724 pub fn deinit(dirs: *Directories, io: Io) void {
725 // The local and global caches could be the same.725 // 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();728 dirs.global_cache.handle.close(io);
729 if (close_local) dirs.local_cache.handle.close();729 if (close_local) dirs.local_cache.handle.close(io);
730 dirs.zig_lib.handle.close();730 dirs.zig_lib.handle.close(io);
731 }731 }
732732
733 /// Returns a `Directories` where `local_cache` is replaced with `global_cache`, intended for733 /// Returns a `Directories` where `local_cache` is replaced with `global_cache`, intended for
...@@ -745,6 +745,7 @@ pub const Directories = struct {...@@ -745,6 +745,7 @@ pub const Directories = struct {
745 /// Uses `std.process.fatal` on error conditions.745 /// Uses `std.process.fatal` on error conditions.
746 pub fn init(746 pub fn init(
747 arena: Allocator,747 arena: Allocator,
748 io: Io,
748 override_zig_lib: ?[]const u8,749 override_zig_lib: ?[]const u8,
749 override_global_cache: ?[]const u8,750 override_global_cache: ?[]const u8,
750 local_cache_strat: union(enum) {751 local_cache_strat: union(enum) {
...@@ -768,30 +769,30 @@ pub const Directories = struct {...@@ -768,30 +769,30 @@ pub const Directories = struct {
768 };769 };
769770
770 const zig_lib: Cache.Directory = d: {771 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");
772 if (wasi) break :d openWasiPreopen(wasi_preopens, "/lib");773 if (wasi) break :d openWasiPreopen(wasi_preopens, "/lib");
773 break :d introspect.findZigLibDirFromSelfExe(arena, cwd, self_exe_path) catch |err| {774 break :d introspect.findZigLibDirFromSelfExe(arena, io, cwd, self_exe_path) catch |err| {
774 fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) });775 fatal("unable to find zig installation directory '{s}': {t}", .{ self_exe_path, err });
775 };776 };
776 };777 };
777778
778 const global_cache: Cache.Directory = d: {779 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");
780 if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache");781 if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache");
781 const path = introspect.resolveGlobalCacheDir(arena) catch |err| {782 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});
783 };784 };
784 break :d openUnresolved(arena, cwd, path, .@"global cache");785 break :d openUnresolved(arena, io, cwd, path, .@"global cache");
785 };786 };
786787
787 const local_cache: Cache.Directory = switch (local_cache_strat) {788 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"),
789 .search => d: {790 .search => d: {
790 const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, cwd) catch |err| {791 const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, io, cwd) catch |err| {
791 fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)});792 fatal("unable to resolve zig cache directory: {t}", .{err});
792 };793 };
793 const path = maybe_path orelse break :d global_cache;794 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");
795 },796 },
796 .global => global_cache,797 .global => global_cache,
797 };798 };
...@@ -814,18 +815,24 @@ pub const Directories = struct {...@@ -814,18 +815,24 @@ pub const Directories = struct {
814 return .{815 return .{
815 .path = if (std.mem.eql(u8, name, ".")) null else name,816 .path = if (std.mem.eql(u8, name, ".")) null else name,
816 .handle = .{817 .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}),
818 },819 },
819 };820 };
820 }821 }
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 {
822 const path = introspect.resolvePath(arena, cwd, &.{unresolved_path}) catch |err| {829 const path = introspect.resolvePath(arena, cwd, &.{unresolved_path}) catch |err| {
823 fatal("unable to resolve {s} directory: {s}", .{ @tagName(thing), @errorName(err) });830 fatal("unable to resolve {s} directory: {s}", .{ @tagName(thing), @errorName(err) });
824 };831 };
825 const nonempty_path = if (path.len == 0) "." else path;832 const nonempty_path = if (path.len == 0) "." else path;
826 const handle_or_err = switch (thing) {833 const handle_or_err = switch (thing) {
827 .@"zig lib" => fs.cwd().openDir(nonempty_path, .{}),834 .@"zig lib" => Io.Dir.cwd().openDir(io, nonempty_path, .{}),
828 .@"global cache", .@"local cache" => fs.cwd().makeOpenPath(nonempty_path, .{}),835 .@"global cache", .@"local cache" => Io.Dir.cwd().createDirPathOpen(io, nonempty_path, .{}),
829 };836 };
830 return .{837 return .{
831 .path = if (path.len == 0) null else path,838 .path = if (path.len == 0) null else path,
...@@ -912,8 +919,8 @@ pub const CrtFile = struct {...@@ -912,8 +919,8 @@ pub const CrtFile = struct {
912 lock: Cache.Lock,919 lock: Cache.Lock,
913 full_object_path: Cache.Path,920 full_object_path: Cache.Path,
914921
915 pub fn deinit(self: *CrtFile, gpa: Allocator) void {922 pub fn deinit(self: *CrtFile, gpa: Allocator, io: Io) void {
916 self.lock.release();923 self.lock.release(io);
917 gpa.free(self.full_object_path.sub_path);924 gpa.free(self.full_object_path.sub_path);
918 self.* = undefined;925 self.* = undefined;
919 }926 }
...@@ -1104,8 +1111,8 @@ pub const CObject = struct {...@@ -1104,8 +1111,8 @@ pub const CObject = struct {
1104 const source_line = source_line: {1111 const source_line = source_line: {
1105 if (diag.src_loc.offset == 0 or diag.src_loc.column == 0) break :source_line 0;1112 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;1114 const file = Io.Dir.cwd().openFile(io, file_name, .{}) catch break :source_line 0;
1108 defer file.close();1115 defer file.close(io);
1109 var buffer: [1024]u8 = undefined;1116 var buffer: [1024]u8 = undefined;
1110 var file_reader = file.reader(io, &buffer);1117 var file_reader = file.reader(io, &buffer);
1111 file_reader.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0;1118 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 {...@@ -1179,8 +1186,8 @@ pub const CObject = struct {
1179 };1186 };
11801187
1181 var buffer: [1024]u8 = undefined;1188 var buffer: [1024]u8 = undefined;
1182 const file = try fs.cwd().openFile(path, .{});1189 const file = try Io.Dir.cwd().openFile(io, path, .{});
1183 defer file.close();1190 defer file.close(io);
1184 var file_reader = file.reader(io, &buffer);1191 var file_reader = file.reader(io, &buffer);
1185 var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface });1192 var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface });
1186 defer bc.deinit();1193 defer bc.deinit();
...@@ -1310,7 +1317,7 @@ pub const CObject = struct {...@@ -1310,7 +1317,7 @@ pub const CObject = struct {
1310 };1317 };
13111318
1312 /// Returns if there was failure.1319 /// 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 {
1314 switch (self.status) {1321 switch (self.status) {
1315 .new => return false,1322 .new => return false,
1316 .failure, .failure_retryable => {1323 .failure, .failure_retryable => {
...@@ -1319,15 +1326,15 @@ pub const CObject = struct {...@@ -1319,15 +1326,15 @@ pub const CObject = struct {
1319 },1326 },
1320 .success => |*success| {1327 .success => |*success| {
1321 gpa.free(success.object_path.sub_path);1328 gpa.free(success.object_path.sub_path);
1322 success.lock.release();1329 success.lock.release(io);
1323 self.status = .new;1330 self.status = .new;
1324 return false;1331 return false;
1325 },1332 },
1326 }1333 }
1327 }1334 }
13281335
1329 pub fn destroy(self: *CObject, gpa: Allocator) void {1336 pub fn destroy(self: *CObject, gpa: Allocator, io: Io) void {
1330 _ = self.clearStatus(gpa);1337 _ = self.clearStatus(gpa, io);
1331 gpa.destroy(self);1338 gpa.destroy(self);
1332 }1339 }
1333};1340};
...@@ -1357,7 +1364,7 @@ pub const Win32Resource = struct {...@@ -1357,7 +1364,7 @@ pub const Win32Resource = struct {
1357 },1364 },
13581365
1359 /// Returns true if there was failure.1366 /// 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 {
1361 switch (self.status) {1368 switch (self.status) {
1362 .new => return false,1369 .new => return false,
1363 .failure, .failure_retryable => {1370 .failure, .failure_retryable => {
...@@ -1366,15 +1373,15 @@ pub const Win32Resource = struct {...@@ -1366,15 +1373,15 @@ pub const Win32Resource = struct {
1366 },1373 },
1367 .success => |*success| {1374 .success => |*success| {
1368 gpa.free(success.res_path);1375 gpa.free(success.res_path);
1369 success.lock.release();1376 success.lock.release(io);
1370 self.status = .new;1377 self.status = .new;
1371 return false;1378 return false;
1372 },1379 },
1373 }1380 }
1374 }1381 }
13751382
1376 pub fn destroy(self: *Win32Resource, gpa: Allocator) void {1383 pub fn destroy(self: *Win32Resource, gpa: Allocator, io: Io) void {
1377 _ = self.clearStatus(gpa);1384 _ = self.clearStatus(gpa, io);
1378 gpa.destroy(self);1385 gpa.destroy(self);
1379 }1386 }
1380};1387};
...@@ -1603,9 +1610,9 @@ const CacheUse = union(CacheMode) {...@@ -1603,9 +1610,9 @@ const CacheUse = union(CacheMode) {
1603 /// Prevents other processes from clobbering files in the output directory.1610 /// Prevents other processes from clobbering files in the output directory.
1604 lock: ?Cache.Lock,1611 lock: ?Cache.Lock,
16051612
1606 fn releaseLock(whole: *Whole) void {1613 fn releaseLock(whole: *Whole, io: Io) void {
1607 if (whole.lock) |*lock| {1614 if (whole.lock) |*lock| {
1608 lock.release();1615 lock.release(io);
1609 whole.lock = null;1616 whole.lock = null;
1610 }1617 }
1611 }1618 }
...@@ -1617,17 +1624,17 @@ const CacheUse = union(CacheMode) {...@@ -1617,17 +1624,17 @@ const CacheUse = union(CacheMode) {
1617 }1624 }
1618 };1625 };
16191626
1620 fn deinit(cu: CacheUse) void {1627 fn deinit(cu: CacheUse, io: Io) void {
1621 switch (cu) {1628 switch (cu) {
1622 .none => |none| {1629 .none => |none| {
1623 assert(none.tmp_artifact_directory == null);1630 assert(none.tmp_artifact_directory == null);
1624 },1631 },
1625 .incremental => |incremental| {1632 .incremental => |incremental| {
1626 incremental.artifact_directory.handle.close();1633 incremental.artifact_directory.handle.close(io);
1627 },1634 },
1628 .whole => |whole| {1635 .whole => |whole| {
1629 assert(whole.tmp_artifact_directory == null);1636 assert(whole.tmp_artifact_directory == null);
1630 whole.releaseLock();1637 whole.releaseLock(io);
1631 },1638 },
1632 }1639 }
1633 }1640 }
...@@ -1872,7 +1879,7 @@ pub const CreateDiagnostic = union(enum) {...@@ -1872,7 +1879,7 @@ pub const CreateDiagnostic = union(enum) {
1872 pub const CreateCachePath = struct {1879 pub const CreateCachePath = struct {
1873 which: enum { local, global },1880 which: enum { local, global },
1874 sub: []const u8,1881 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),
1876 };1883 };
1877 pub fn format(diag: CreateDiagnostic, w: *Writer) Writer.Error!void {1884 pub fn format(diag: CreateDiagnostic, w: *Writer) Writer.Error!void {
1878 switch (diag) {1885 switch (diag) {
...@@ -1896,13 +1903,17 @@ pub const CreateDiagnostic = union(enum) {...@@ -1896,13 +1903,17 @@ pub const CreateDiagnostic = union(enum) {
1896 return error.CreateFail;1903 return error.CreateFail;
1897 }1904 }
1898};1905};
1899pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, options: CreateOptions) error{1906
1907pub const CreateError = error{
1900 OutOfMemory,1908 OutOfMemory,
1909 Canceled,
1901 Unexpected,1910 Unexpected,
1902 CurrentWorkingDirectoryUnlinked,1911 CurrentWorkingDirectoryUnlinked,
1903 /// An error has been stored to `diag`.1912 /// An error has been stored to `diag`.
1904 CreateFail,1913 CreateFail,
1905}!*Compilation {1914};
1915
1916pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, options: CreateOptions) CreateError!*Compilation {
1906 const output_mode = options.config.output_mode;1917 const output_mode = options.config.output_mode;
1907 const is_dyn_lib = switch (output_mode) {1918 const is_dyn_lib = switch (output_mode) {
1908 .Obj, .Exe => false,1919 .Obj, .Exe => false,
...@@ -1950,6 +1961,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,...@@ -1950,6 +1961,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
19501961
1951 const libc_dirs = std.zig.LibCDirs.detect(1962 const libc_dirs = std.zig.LibCDirs.detect(
1952 arena,1963 arena,
1964 io,
1953 options.dirs.zig_lib.path.?,1965 options.dirs.zig_lib.path.?,
1954 target,1966 target,
1955 options.root_mod.resolved_target.is_native_abi,1967 options.root_mod.resolved_target.is_native_abi,
...@@ -2080,13 +2092,17 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,...@@ -2080,13 +2092,17 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
2080 }2092 }
20812093
2082 if (options.verbose_llvm_cpu_features) {2094 if (options.verbose_llvm_cpu_features) {
2083 if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: {2095 if (options.root_mod.resolved_target.llvm_cpu_features) |cf| {
2084 const stderr_w, _ = std.debug.lockStderrWriter(&.{});2096 const stderr = try io.lockStderr(&.{}, null);
2085 defer std.debug.unlockStderrWriter();2097 defer io.unlockStderr();
2086 stderr_w.print("compilation: {s}\n", .{options.root_name}) catch break :print;2098 const w = &stderr.file_writer.interface;
2087 stderr_w.print(" target: {s}\n", .{try target.zigTriple(arena)}) catch break :print;2099 printVerboseLlvmCpuFeatures(w, arena, options.root_name, target, cf) catch |err| switch (err) {
2088 stderr_w.print(" cpu: {s}\n", .{target.cpu.model.name}) catch break :print;2100 error.WriteFailed => switch (stderr.file_writer.err.?) {
2089 stderr_w.print(" features: {s}\n", .{cf}) catch {};2101 error.Canceled => |e| return e,
2102 else => {},
2103 },
2104 error.OutOfMemory => |e| return e,
2105 };
2090 }2106 }
2091 }2107 }
20922108
...@@ -2104,16 +2120,16 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,...@@ -2104,16 +2120,16 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
2104 cache.* = .{2120 cache.* = .{
2105 .gpa = gpa,2121 .gpa = gpa,
2106 .io = io,2122 .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| {
2108 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } });2124 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } });
2109 },2125 },
2110 };2126 };
2111 // These correspond to std.zig.Server.Message.PathPrefix.2127 // 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() });
2113 cache.addPrefix(options.dirs.zig_lib);2129 cache.addPrefix(options.dirs.zig_lib);
2114 cache.addPrefix(options.dirs.local_cache);2130 cache.addPrefix(options.dirs.local_cache);
2115 cache.addPrefix(options.dirs.global_cache);2131 cache.addPrefix(options.dirs.global_cache);
2116 errdefer cache.manifest_dir.close();2132 errdefer cache.manifest_dir.close(io);
21172133
2118 // This is shared hasher state common to zig source and all C source files.2134 // This is shared hasher state common to zig source and all C source files.
2119 cache.hash.addBytes(build_options.version);2135 cache.hash.addBytes(build_options.version);
...@@ -2154,18 +2170,18 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,...@@ -2154,18 +2170,18 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
2154 // to redundantly happen for each AstGen operation.2170 // to redundantly happen for each AstGen operation.
2155 const zir_sub_dir = "z";2171 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| {
2158 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = zir_sub_dir, .err = err } });2174 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = zir_sub_dir, .err = err } });
2159 };2175 };
2160 errdefer local_zir_dir.close();2176 errdefer local_zir_dir.close(io);
2161 const local_zir_cache: Cache.Directory = .{2177 const local_zir_cache: Cache.Directory = .{
2162 .handle = local_zir_dir,2178 .handle = local_zir_dir,
2163 .path = try options.dirs.local_cache.join(arena, &.{zir_sub_dir}),2179 .path = try options.dirs.local_cache.join(arena, &.{zir_sub_dir}),
2164 };2180 };
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| {
2166 return diag.fail(.{ .create_cache_path = .{ .which = .global, .sub = zir_sub_dir, .err = err } });2182 return diag.fail(.{ .create_cache_path = .{ .which = .global, .sub = zir_sub_dir, .err = err } });
2167 };2183 };
2168 errdefer global_zir_dir.close();2184 errdefer global_zir_dir.close(io);
2169 const global_zir_cache: Cache.Directory = .{2185 const global_zir_cache: Cache.Directory = .{
2170 .handle = global_zir_dir,2186 .handle = global_zir_dir,
2171 .path = try options.dirs.global_cache.join(arena, &.{zir_sub_dir}),2187 .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,...@@ -2433,10 +2449,10 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
2433 const digest = hash.final();2449 const digest = hash.final();
24342450
2435 const artifact_sub_dir = "o" ++ fs.path.sep_str ++ digest;2451 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| {
2437 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = artifact_sub_dir, .err = err } });2453 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = artifact_sub_dir, .err = err } });
2438 };2454 };
2439 errdefer artifact_dir.close();2455 errdefer artifact_dir.close(io);
2440 const artifact_directory: Cache.Directory = .{2456 const artifact_directory: Cache.Directory = .{
2441 .handle = artifact_dir,2457 .handle = artifact_dir,
2442 .path = try options.dirs.local_cache.join(arena, &.{artifact_sub_dir}),2458 .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,...@@ -2687,12 +2703,26 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
2687 return comp;2703 return comp;
2688}2704}
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
2690pub fn destroy(comp: *Compilation) void {2719pub fn destroy(comp: *Compilation) void {
2691 const gpa = comp.gpa;2720 const gpa = comp.gpa;
2721 const io = comp.io;
26922722
2693 if (comp.bin_file) |lf| lf.destroy();2723 if (comp.bin_file) |lf| lf.destroy();
2694 if (comp.zcu) |zcu| zcu.deinit();2724 if (comp.zcu) |zcu| zcu.deinit();
2695 comp.cache_use.deinit();2725 comp.cache_use.deinit(io);
26962726
2697 for (&comp.work_queues) |*work_queue| work_queue.deinit(gpa);2727 for (&comp.work_queues) |*work_queue| work_queue.deinit(gpa);
2698 comp.c_object_work_queue.deinit(gpa);2728 comp.c_object_work_queue.deinit(gpa);
...@@ -2705,36 +2735,36 @@ pub fn destroy(comp: *Compilation) void {...@@ -2705,36 +2735,36 @@ pub fn destroy(comp: *Compilation) void {
2705 var it = comp.crt_files.iterator();2735 var it = comp.crt_files.iterator();
2706 while (it.next()) |entry| {2736 while (it.next()) |entry| {
2707 gpa.free(entry.key_ptr.*);2737 gpa.free(entry.key_ptr.*);
2708 entry.value_ptr.deinit(gpa);2738 entry.value_ptr.deinit(gpa, io);
2709 }2739 }
2710 comp.crt_files.deinit(gpa);2740 comp.crt_files.deinit(gpa);
2711 }2741 }
2712 if (comp.libcxx_static_lib) |*crt_file| crt_file.deinit(gpa);2742 if (comp.libcxx_static_lib) |*crt_file| crt_file.deinit(gpa, io);
2713 if (comp.libcxxabi_static_lib) |*crt_file| crt_file.deinit(gpa);2743 if (comp.libcxxabi_static_lib) |*crt_file| crt_file.deinit(gpa, io);
2714 if (comp.libunwind_static_lib) |*crt_file| crt_file.deinit(gpa);2744 if (comp.libunwind_static_lib) |*crt_file| crt_file.deinit(gpa, io);
2715 if (comp.tsan_lib) |*crt_file| crt_file.deinit(gpa);2745 if (comp.tsan_lib) |*crt_file| crt_file.deinit(gpa, io);
2716 if (comp.ubsan_rt_lib) |*crt_file| crt_file.deinit(gpa);2746 if (comp.ubsan_rt_lib) |*crt_file| crt_file.deinit(gpa, io);
2717 if (comp.ubsan_rt_obj) |*crt_file| crt_file.deinit(gpa);2747 if (comp.ubsan_rt_obj) |*crt_file| crt_file.deinit(gpa, io);
2718 if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa);2748 if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa, io);
2719 if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa);2749 if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa, io);
2720 if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa);2750 if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa, io);
2721 if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa);2751 if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa, io);
2722 if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa);2752 if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa, io);
27232753
2724 if (comp.glibc_so_files) |*glibc_file| {2754 if (comp.glibc_so_files) |*glibc_file| {
2725 glibc_file.deinit(gpa);2755 glibc_file.deinit(gpa, io);
2726 }2756 }
27272757
2728 if (comp.freebsd_so_files) |*freebsd_file| {2758 if (comp.freebsd_so_files) |*freebsd_file| {
2729 freebsd_file.deinit(gpa);2759 freebsd_file.deinit(gpa, io);
2730 }2760 }
27312761
2732 if (comp.netbsd_so_files) |*netbsd_file| {2762 if (comp.netbsd_so_files) |*netbsd_file| {
2733 netbsd_file.deinit(gpa);2763 netbsd_file.deinit(gpa, io);
2734 }2764 }
27352765
2736 for (comp.c_object_table.keys()) |key| {2766 for (comp.c_object_table.keys()) |key| {
2737 key.destroy(gpa);2767 key.destroy(gpa, io);
2738 }2768 }
2739 comp.c_object_table.deinit(gpa);2769 comp.c_object_table.deinit(gpa);
27402770
...@@ -2744,7 +2774,7 @@ pub fn destroy(comp: *Compilation) void {...@@ -2744,7 +2774,7 @@ pub fn destroy(comp: *Compilation) void {
2744 comp.failed_c_objects.deinit(gpa);2774 comp.failed_c_objects.deinit(gpa);
27452775
2746 for (comp.win32_resource_table.keys()) |key| {2776 for (comp.win32_resource_table.keys()) |key| {
2747 key.destroy(gpa);2777 key.destroy(gpa, io);
2748 }2778 }
2749 comp.win32_resource_table.deinit(gpa);2779 comp.win32_resource_table.deinit(gpa);
27502780
...@@ -2760,7 +2790,7 @@ pub fn destroy(comp: *Compilation) void {...@@ -2760,7 +2790,7 @@ pub fn destroy(comp: *Compilation) void {
27602790
2761 comp.clearMiscFailures();2791 comp.clearMiscFailures();
27622792
2763 comp.cache_parent.manifest_dir.close();2793 comp.cache_parent.manifest_dir.close(io);
2764}2794}
27652795
2766pub fn clearMiscFailures(comp: *Compilation) void {2796pub fn clearMiscFailures(comp: *Compilation) void {
...@@ -2791,10 +2821,12 @@ pub fn hotCodeSwap(...@@ -2791,10 +2821,12 @@ pub fn hotCodeSwap(
2791}2821}
27922822
2793fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void {2823fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void {
2824 const io = comp.io;
2825
2794 switch (comp.cache_use) {2826 switch (comp.cache_use) {
2795 .none => |none| {2827 .none => |none| {
2796 if (none.tmp_artifact_directory) |*tmp_dir| {2828 if (none.tmp_artifact_directory) |*tmp_dir| {
2797 tmp_dir.handle.close();2829 tmp_dir.handle.close(io);
2798 none.tmp_artifact_directory = null;2830 none.tmp_artifact_directory = null;
2799 if (dev.env == .bootstrap) {2831 if (dev.env == .bootstrap) {
2800 // zig1 uses `CacheMode.none`, but it doesn't need to know how to delete2832 // 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 {...@@ -2813,12 +2845,9 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void {
2813 return;2845 return;
2814 }2846 }
2815 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);2847 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| {2848 comp.dirs.local_cache.handle.deleteTree(io, tmp_dir_sub_path) catch |err| {
2817 log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{2849 log.warn("failed to delete temporary directory '{s}{c}{s}': {t}", .{
2818 comp.dirs.local_cache.path orelse ".",2850 comp.dirs.local_cache.path orelse ".", fs.path.sep, tmp_dir_sub_path, err,
2819 fs.path.sep,
2820 tmp_dir_sub_path,
2821 @errorName(err),
2822 });2851 });
2823 };2852 };
2824 }2853 }
...@@ -2834,15 +2863,12 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void {...@@ -2834,15 +2863,12 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void {
2834 comp.bin_file = null;2863 comp.bin_file = null;
2835 }2864 }
2836 if (whole.tmp_artifact_directory) |*tmp_dir| {2865 if (whole.tmp_artifact_directory) |*tmp_dir| {
2837 tmp_dir.handle.close();2866 tmp_dir.handle.close(io);
2838 whole.tmp_artifact_directory = null;2867 whole.tmp_artifact_directory = null;
2839 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);2868 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| {2869 comp.dirs.local_cache.handle.deleteTree(io, tmp_dir_sub_path) catch |err| {
2841 log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{2870 log.warn("failed to delete temporary directory '{s}{c}{s}': {t}", .{
2842 comp.dirs.local_cache.path orelse ".",2871 comp.dirs.local_cache.path orelse ".", fs.path.sep, tmp_dir_sub_path, err,
2843 fs.path.sep,
2844 tmp_dir_sub_path,
2845 @errorName(err),
2846 });2872 });
2847 };2873 };
2848 }2874 }
...@@ -2891,7 +2917,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -2891,7 +2917,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
2891 tmp_dir_rand_int = std.crypto.random.int(u64);2917 tmp_dir_rand_int = std.crypto.random.int(u64);
2892 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);2918 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
2893 const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});2919 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| {
2895 return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err });2921 return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err });
2896 };2922 };
2897 break :d .{ .path = path, .handle = handle };2923 break :d .{ .path = path, .handle = handle };
...@@ -2901,7 +2927,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -2901,7 +2927,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
2901 .whole => |whole| {2927 .whole => |whole| {
2902 assert(comp.bin_file == null);2928 assert(comp.bin_file == null);
2903 // We are about to obtain this lock, so here we give other processes a chance first.2929 // We are about to obtain this lock, so here we give other processes a chance first.
2904 whole.releaseLock();2930 whole.releaseLock(io);
29052931
2906 man = comp.cache_parent.obtain();2932 man = comp.cache_parent.obtain();
2907 whole.cache_manifest = &man;2933 whole.cache_manifest = &man;
...@@ -2972,7 +2998,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -2972,7 +2998,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
2972 tmp_dir_rand_int = std.crypto.random.int(u64);2998 tmp_dir_rand_int = std.crypto.random.int(u64);
2973 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);2999 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
2974 const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});3000 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| {
2976 return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err });3002 return comp.setMiscFailure(.open_output, "failed to create output directory '{s}': {t}", .{ path, err });
2977 };3003 };
2978 break :d .{ .path = path, .handle = handle };3004 break :d .{ .path = path, .handle = handle };
...@@ -3087,17 +3113,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -3087,17 +3113,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
3087 }3113 }
30883114
3089 if (build_options.enable_debug_extensions and comp.verbose_intern_pool) {3115 if (build_options.enable_debug_extensions and comp.verbose_intern_pool) {
3090 std.debug.print("intern pool stats for '{s}':\n", .{3116 std.debug.print("intern pool stats for '{s}':\n", .{comp.root_name});
3091 comp.root_name,
3092 });
3093 zcu.intern_pool.dump();3117 zcu.intern_pool.dump();
3094 }3118 }
30953119
3096 if (build_options.enable_debug_extensions and comp.verbose_generic_instances) {3120 if (build_options.enable_debug_extensions and comp.verbose_generic_instances) {
3097 std.debug.print("generic instances for '{s}:0x{x}':\n", .{3121 std.debug.print("generic instances for '{s}:0x{x}':\n", .{ comp.root_name, @intFromPtr(zcu) });
3098 comp.root_name,
3099 @intFromPtr(zcu),
3100 });
3101 zcu.intern_pool.dumpGenericInstances(gpa);3122 zcu.intern_pool.dumpGenericInstances(gpa);
3102 }3123 }
3103 }3124 }
...@@ -3152,7 +3173,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -3152,7 +3173,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
3152 // the file handle and re-open it in the follow up call to3173 // the file handle and re-open it in the follow up call to
3153 // `makeWritable`.3174 // `makeWritable`.
3154 if (lf.file) |f| {3175 if (lf.file) |f| {
3155 f.close();3176 f.close(io);
3156 lf.file = null;3177 lf.file = null;
31573178
3158 if (lf.closeDebugInfo()) break :w .lf_and_debug;3179 if (lf.closeDebugInfo()) break :w .lf_and_debug;
...@@ -3165,12 +3186,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -3165,12 +3186,12 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
31653186
3166 // Rename the temporary directory into place.3187 // Rename the temporary directory into place.
3167 // Close tmp dir and link.File to avoid open handle during rename.3188 // 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);
3169 whole.tmp_artifact_directory = null;3190 whole.tmp_artifact_directory = null;
3170 const s = fs.path.sep_str;3191 const s = fs.path.sep_str;
3171 const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int);3192 const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int);
3172 const o_sub_path = "o" ++ s ++ hex_digest;3193 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| {
3174 return comp.setMiscFailure(3195 return comp.setMiscFailure(
3175 .rename_results,3196 .rename_results,
3176 "failed to rename compilation results ('{f}{s}') into local cache ('{f}{s}'): {t}",3197 "failed to rename compilation results ('{f}{s}') into local cache ('{f}{s}'): {t}",
...@@ -3300,11 +3321,8 @@ pub fn resolveEmitPathFlush(...@@ -3300,11 +3321,8 @@ pub fn resolveEmitPathFlush(
3300 },3321 },
3301 }3322 }
3302}3323}
3303fn flush(3324
3304 comp: *Compilation,3325fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancelable || Allocator.Error)!void {
3305 arena: Allocator,
3306 tid: Zcu.PerThread.Id,
3307) Allocator.Error!void {
3308 const io = comp.io;3326 const io = comp.io;
3309 if (comp.zcu) |zcu| {3327 if (comp.zcu) |zcu| {
3310 if (zcu.llvm_object) |llvm_object| {3328 if (zcu.llvm_object) |llvm_object| {
...@@ -3370,7 +3388,7 @@ fn flush(...@@ -3370,7 +3388,7 @@ fn flush(
3370 // This is needed before reading the error flags.3388 // This is needed before reading the error flags.
3371 lf.flush(arena, tid, comp.link_prog_node) catch |err| switch (err) {3389 lf.flush(arena, tid, comp.link_prog_node) catch |err| switch (err) {
3372 error.LinkFailure => {}, // Already reported.3390 error.LinkFailure => {}, // Already reported.
3373 error.OutOfMemory => return error.OutOfMemory,3391 error.OutOfMemory, error.Canceled => |e| return e,
3374 };3392 };
3375 }3393 }
3376 if (comp.zcu) |zcu| {3394 if (comp.zcu) |zcu| {
...@@ -3389,17 +3407,19 @@ fn flush(...@@ -3389,17 +3407,19 @@ fn flush(
3389/// implementation at the bottom of this function.3407/// implementation at the bottom of this function.
3390/// This function is only called when CacheMode is `whole`.3408/// This function is only called when CacheMode is `whole`.
3391fn renameTmpIntoCache(3409fn renameTmpIntoCache(
3410 io: Io,
3392 cache_directory: Cache.Directory,3411 cache_directory: Cache.Directory,
3393 tmp_dir_sub_path: []const u8,3412 tmp_dir_sub_path: []const u8,
3394 o_sub_path: []const u8,3413 o_sub_path: []const u8,
3395) !void {3414) !void {
3396 var seen_eaccess = false;3415 var seen_eaccess = false;
3397 while (true) {3416 while (true) {
3398 fs.rename(3417 Io.Dir.rename(
3399 cache_directory.handle,3418 cache_directory.handle,
3400 tmp_dir_sub_path,3419 tmp_dir_sub_path,
3401 cache_directory.handle,3420 cache_directory.handle,
3402 o_sub_path,3421 o_sub_path,
3422 io,
3403 ) catch |err| switch (err) {3423 ) catch |err| switch (err) {
3404 // On Windows, rename fails with `AccessDenied` rather than `PathAlreadyExists`.3424 // On Windows, rename fails with `AccessDenied` rather than `PathAlreadyExists`.
3405 // See https://github.com/ziglang/zig/issues/83623425 // See https://github.com/ziglang/zig/issues/8362
...@@ -3407,17 +3427,17 @@ fn renameTmpIntoCache(...@@ -3407,17 +3427,17 @@ fn renameTmpIntoCache(
3407 .windows => {3427 .windows => {
3408 if (seen_eaccess) return error.AccessDenied;3428 if (seen_eaccess) return error.AccessDenied;
3409 seen_eaccess = true;3429 seen_eaccess = true;
3410 try cache_directory.handle.deleteTree(o_sub_path);3430 try cache_directory.handle.deleteTree(io, o_sub_path);
3411 continue;3431 continue;
3412 },3432 },
3413 else => return error.AccessDenied,3433 else => return error.AccessDenied,
3414 },3434 },
3415 error.PathAlreadyExists => {3435 error.PathAlreadyExists => {
3416 try cache_directory.handle.deleteTree(o_sub_path);3436 try cache_directory.handle.deleteTree(io, o_sub_path);
3417 continue;3437 continue;
3418 },3438 },
3419 error.FileNotFound => {3439 error.FileNotFound => {
3420 try cache_directory.handle.makePath("o");3440 try cache_directory.handle.createDirPath(io, "o");
3421 continue;3441 continue;
3422 },3442 },
3423 else => |e| return e,3443 else => |e| return e,
...@@ -3592,6 +3612,7 @@ fn emitFromCObject(...@@ -3592,6 +3612,7 @@ fn emitFromCObject(
3592 new_ext: []const u8,3612 new_ext: []const u8,
3593 unresolved_emit_path: []const u8,3613 unresolved_emit_path: []const u8,
3594) Allocator.Error!void {3614) Allocator.Error!void {
3615 const io = comp.io;
3595 // The dirname and stem (i.e. everything but the extension), of the sub path of the C object.3616 // The dirname and stem (i.e. everything but the extension), of the sub path of the C object.
3596 // We'll append `new_ext` to it to get the path to the right thing (asm, LLVM IR, etc).3617 // We'll append `new_ext` to it to get the path to the right thing (asm, LLVM IR, etc).
3597 const c_obj_dir_and_stem: []const u8 = p: {3618 const c_obj_dir_and_stem: []const u8 = p: {
...@@ -3601,23 +3622,18 @@ fn emitFromCObject(...@@ -3601,23 +3622,18 @@ fn emitFromCObject(
3601 };3622 };
3602 const src_path: Cache.Path = .{3623 const src_path: Cache.Path = .{
3603 .root_dir = c_obj_path.root_dir,3624 .root_dir = c_obj_path.root_dir,
3604 .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{3625 .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ c_obj_dir_and_stem, new_ext }),
3605 c_obj_dir_and_stem,
3606 new_ext,
3607 }),
3608 };3626 };
3609 const emit_path = comp.resolveEmitPath(unresolved_emit_path);3627 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,
3612 src_path.sub_path,3631 src_path.sub_path,
3613 emit_path.root_dir.handle,3632 emit_path.root_dir.handle,
3614 emit_path.sub_path,3633 emit_path.sub_path,
3634 io,
3615 .{},3635 .{},
3616 ) catch |err| log.err("unable to copy '{f}' to '{f}': {s}", .{3636 ) catch |err| log.err("unable to copy '{f}' to '{f}': {t}", .{ src_path, emit_path, err });
3617 src_path,
3618 emit_path,
3619 @errorName(err),
3620 });
3621}3637}
36223638
3623/// Having the file open for writing is problematic as far as executing the3639/// Having the file open for writing is problematic as far as executing the
...@@ -3673,6 +3689,7 @@ pub fn saveState(comp: *Compilation) !void {...@@ -3673,6 +3689,7 @@ pub fn saveState(comp: *Compilation) !void {
3673 const lf = comp.bin_file orelse return;3689 const lf = comp.bin_file orelse return;
36743690
3675 const gpa = comp.gpa;3691 const gpa = comp.gpa;
3692 const io = comp.io;
36763693
3677 var bufs = std.array_list.Managed([]const u8).init(gpa);3694 var bufs = std.array_list.Managed([]const u8).init(gpa);
3678 defer bufs.deinit();3695 defer bufs.deinit();
...@@ -3893,7 +3910,7 @@ pub fn saveState(comp: *Compilation) !void {...@@ -3893,7 +3910,7 @@ pub fn saveState(comp: *Compilation) !void {
3893 // Using an atomic file prevents a crash or power failure from corrupting3910 // Using an atomic file prevents a crash or power failure from corrupting
3894 // the previous incremental compilation state.3911 // the previous incremental compilation state.
3895 var write_buffer: [1024]u8 = undefined;3912 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 });
3897 defer af.deinit();3914 defer af.deinit();
3898 try af.file_writer.interface.writeVecAll(bufs.items);3915 try af.file_writer.interface.writeVecAll(bufs.items);
3899 try af.finish();3916 try af.finish();
...@@ -4251,12 +4268,13 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle {...@@ -4251,12 +4268,13 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle {
4251 // However, we haven't reported any such error.4268 // However, we haven't reported any such error.
4252 // This is a compiler bug.4269 // This is a compiler bug.
4253 print_ctx: {4270 print_ctx: {
4254 var stderr_w, _ = std.debug.lockStderrWriter(&.{});4271 const stderr = std.debug.lockStderr(&.{}).terminal();
4255 defer std.debug.unlockStderrWriter();4272 defer std.debug.unlockStderr();
4256 stderr_w.writeAll("referenced transitive analysis errors, but none actually emitted\n") catch break :print_ctx;4273 const w = stderr.writer;
4257 stderr_w.print("{f} [transitive failure]\n", .{zcu.fmtAnalUnit(failed_unit)}) catch break :print_ctx;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;
4258 while (ref) |r| {4276 while (ref) |r| {
4259 stderr_w.print("referenced by: {f}{s}\n", .{4277 w.print("referenced by: {f}{s}\n", .{
4260 zcu.fmtAnalUnit(r.referencer),4278 zcu.fmtAnalUnit(r.referencer),
4261 if (zcu.transitive_failed_analysis.contains(r.referencer)) " [transitive failure]" else "",4279 if (zcu.transitive_failed_analysis.contains(r.referencer)) " [transitive failure]" else "",
4262 }) catch break :print_ctx;4280 }) catch break :print_ctx;
...@@ -5038,7 +5056,9 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node...@@ -5038,7 +5056,9 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node
5038 }5056 }
50395057
5040 prelink_group.wait(io);5058 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 };
5042}5062}
50435063
5044const JobError = Allocator.Error || Io.Cancelable;5064const JobError = Allocator.Error || Io.Cancelable;
...@@ -5211,13 +5231,10 @@ fn processOneJob(...@@ -5211,13 +5231,10 @@ fn processOneJob(
5211 }5231 }
5212}5232}
52135233
5214fn createDepFile(5234fn createDepFile(comp: *Compilation, depfile: []const u8, binfile: Cache.Path) anyerror!void {
5215 comp: *Compilation,5235 const io = comp.io;
5216 depfile: []const u8,
5217 binfile: Cache.Path,
5218) anyerror!void {
5219 var buf: [4096]u8 = undefined;5236 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 });
5221 defer af.deinit();5238 defer af.deinit();
52225239
5223 comp.writeDepFile(binfile, &af.file_writer.interface) catch return af.file_writer.err.?;5240 comp.writeDepFile(binfile, &af.file_writer.interface) catch return af.file_writer.err.?;
...@@ -5258,39 +5275,35 @@ fn workerDocsCopy(comp: *Compilation) void {...@@ -5258,39 +5275,35 @@ fn workerDocsCopy(comp: *Compilation) void {
52585275
5259fn docsCopyFallible(comp: *Compilation) anyerror!void {5276fn docsCopyFallible(comp: *Compilation) anyerror!void {
5260 const zcu = comp.zcu orelse return comp.lockAndSetMiscFailure(.docs_copy, "no Zig code to document", .{});5277 const zcu = comp.zcu orelse return comp.lockAndSetMiscFailure(.docs_copy, "no Zig code to document", .{});
5278 const io = comp.io;
52615279
5262 const docs_path = comp.resolveEmitPath(comp.emit_docs.?);5280 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| {
5264 return comp.lockAndSetMiscFailure(5282 return comp.lockAndSetMiscFailure(
5265 .docs_copy,5283 .docs_copy,
5266 "unable to create output directory '{f}': {s}",5284 "unable to create output directory '{f}': {s}",
5267 .{ docs_path, @errorName(err) },5285 .{ docs_path, @errorName(err) },
5268 );5286 );
5269 };5287 };
5270 defer out_dir.close();5288 defer out_dir.close(io);
52715289
5272 for (&[_][]const u8{ "docs/main.js", "docs/index.html" }) |sub_path| {5290 for (&[_][]const u8{ "docs/main.js", "docs/index.html" }) |sub_path| {
5273 const basename = fs.path.basename(sub_path);5291 const basename = fs.path.basename(sub_path);
5274 comp.dirs.zig_lib.handle.copyFile(sub_path, out_dir, basename, .{}) catch |err| {5292 comp.dirs.zig_lib.handle.copyFile(sub_path, out_dir, basename, io, .{}) catch |err|
5275 comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {s}", .{5293 return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {t}", .{ sub_path, err });
5276 sub_path,
5277 @errorName(err),
5278 });
5279 return;
5280 };
5281 }5294 }
52825295
5283 var tar_file = out_dir.createFile("sources.tar", .{}) catch |err| {5296 var tar_file = out_dir.createFile(io, "sources.tar", .{}) catch |err| {
5284 return comp.lockAndSetMiscFailure(5297 return comp.lockAndSetMiscFailure(
5285 .docs_copy,5298 .docs_copy,
5286 "unable to create '{f}/sources.tar': {s}",5299 "unable to create '{f}/sources.tar': {s}",
5287 .{ docs_path, @errorName(err) },5300 .{ docs_path, @errorName(err) },
5288 );5301 );
5289 };5302 };
5290 defer tar_file.close();5303 defer tar_file.close(io);
52915304
5292 var buffer: [1024]u8 = undefined;5305 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
5295 var seen_table: std.AutoArrayHashMapUnmanaged(*Package.Module, []const u8) = .empty;5308 var seen_table: std.AutoArrayHashMapUnmanaged(*Package.Module, []const u8) = .empty;
5296 defer seen_table.deinit(comp.gpa);5309 defer seen_table.deinit(comp.gpa);
...@@ -5321,17 +5334,17 @@ fn docsCopyModule(...@@ -5321,17 +5334,17 @@ fn docsCopyModule(
5321 comp: *Compilation,5334 comp: *Compilation,
5322 module: *Package.Module,5335 module: *Package.Module,
5323 name: []const u8,5336 name: []const u8,
5324 tar_file_writer: *fs.File.Writer,5337 tar_file_writer: *Io.File.Writer,
5325) !void {5338) !void {
5326 const io = comp.io;5339 const io = comp.io;
5327 const root = module.root;5340 const root = module.root;
5328 var mod_dir = d: {5341 var mod_dir = d: {
5329 const root_dir, const sub_path = root.openInfo(comp.dirs);5342 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 });
5331 } catch |err| {5344 } catch |err| {
5332 return comp.lockAndSetMiscFailure(.docs_copy, "unable to open directory '{f}': {t}", .{ root.fmt(comp), err });5345 return comp.lockAndSetMiscFailure(.docs_copy, "unable to open directory '{f}': {t}", .{ root.fmt(comp), err });
5333 };5346 };
5334 defer mod_dir.close();5347 defer mod_dir.close(io);
53355348
5336 var walker = try mod_dir.walk(comp.gpa);5349 var walker = try mod_dir.walk(comp.gpa);
5337 defer walker.deinit();5350 defer walker.deinit();
...@@ -5341,7 +5354,7 @@ fn docsCopyModule(...@@ -5341,7 +5354,7 @@ fn docsCopyModule(
53415354
5342 var buffer: [1024]u8 = undefined;5355 var buffer: [1024]u8 = undefined;
53435356
5344 while (try walker.next()) |entry| {5357 while (try walker.next(io)) |entry| {
5345 switch (entry.kind) {5358 switch (entry.kind) {
5346 .file => {5359 .file => {
5347 if (!std.mem.endsWith(u8, entry.basename, ".zig")) continue;5360 if (!std.mem.endsWith(u8, entry.basename, ".zig")) continue;
...@@ -5350,14 +5363,14 @@ fn docsCopyModule(...@@ -5350,14 +5363,14 @@ fn docsCopyModule(
5350 },5363 },
5351 else => continue,5364 else => continue,
5352 }5365 }
5353 var file = mod_dir.openFile(entry.path, .{}) catch |err| {5366 var file = mod_dir.openFile(io, entry.path, .{}) catch |err| {
5354 return comp.lockAndSetMiscFailure(.docs_copy, "unable to open {f}{s}: {t}", .{5367 return comp.lockAndSetMiscFailure(.docs_copy, "unable to open {f}{s}: {t}", .{
5355 root.fmt(comp), entry.path, err,5368 root.fmt(comp), entry.path, err,
5356 });5369 });
5357 };5370 };
5358 defer file.close();5371 defer file.close(io);
5359 const stat = try file.stat();5372 const stat = try file.stat(io);
5360 var file_reader: fs.File.Reader = .initSize(file.adaptToNewApi(), io, &buffer, stat.size);5373 var file_reader: Io.File.Reader = .initSize(file, io, &buffer, stat.size);
53615374
5362 archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime) catch |err| {5375 archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime) catch |err| {
5363 return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive {f}{s}: {t}", .{5376 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...@@ -5496,13 +5509,13 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU
5496 try comp.updateSubCompilation(sub_compilation, .docs_wasm, prog_node);5509 try comp.updateSubCompilation(sub_compilation, .docs_wasm, prog_node);
54975510
5498 var crt_file = try sub_compilation.toCrtFile();5511 var crt_file = try sub_compilation.toCrtFile();
5499 defer crt_file.deinit(gpa);5512 defer crt_file.deinit(gpa, io);
55005513
5501 const docs_bin_file = crt_file.full_object_path;5514 const docs_bin_file = crt_file.full_object_path;
5502 assert(docs_bin_file.sub_path.len > 0); // emitted binary is not a directory5515 assert(docs_bin_file.sub_path.len > 0); // emitted binary is not a directory
55035516
5504 const docs_path = comp.resolveEmitPath(comp.emit_docs.?);5517 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| {
5506 comp.lockAndSetMiscFailure(5519 comp.lockAndSetMiscFailure(
5507 .docs_copy,5520 .docs_copy,
5508 "unable to create output directory '{f}': {t}",5521 "unable to create output directory '{f}': {t}",
...@@ -5510,12 +5523,14 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU...@@ -5510,12 +5523,14 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU
5510 );5523 );
5511 return error.AlreadyReported;5524 return error.AlreadyReported;
5512 };5525 };
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,
5516 crt_file.full_object_path.sub_path,5530 crt_file.full_object_path.sub_path,
5517 out_dir,5531 out_dir,
5518 "main.wasm",5532 "main.wasm",
5533 io,
5519 .{},5534 .{},
5520 ) catch |err| {5535 ) catch |err| {
5521 comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{f}' to '{f}': {t}", .{5536 comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{f}' to '{f}': {t}", .{
...@@ -5692,8 +5707,8 @@ pub fn translateC(...@@ -5692,8 +5707,8 @@ pub fn translateC(
5692 const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));5707 const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));
5693 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;5708 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;
5694 const cache_dir = comp.dirs.local_cache.handle;5709 const cache_dir = comp.dirs.local_cache.handle;
5695 var cache_tmp_dir = try cache_dir.makeOpenPath(tmp_sub_path, .{});5710 var cache_tmp_dir = try cache_dir.createDirPathOpen(io, tmp_sub_path, .{});
5696 defer cache_tmp_dir.close();5711 defer cache_tmp_dir.close(io);
56975712
5698 const translated_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, translated_basename });5713 const translated_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, translated_basename });
5699 const source_path = switch (source) {5714 const source_path = switch (source) {
...@@ -5702,7 +5717,7 @@ pub fn translateC(...@@ -5702,7 +5717,7 @@ pub fn translateC(
5702 const out_h_sub_path = tmp_sub_path ++ fs.path.sep_str ++ cimport_basename;5717 const out_h_sub_path = tmp_sub_path ++ fs.path.sep_str ++ cimport_basename;
5703 const out_h_path = try comp.dirs.local_cache.join(arena, &.{out_h_sub_path});5718 const out_h_path = try comp.dirs.local_cache.join(arena, &.{out_h_sub_path});
5704 if (comp.verbose_cimport) log.info("writing C import source to {s}", .{out_h_path});5719 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 });
5706 break :path out_h_path;5721 break :path out_h_path;
5707 },5722 },
5708 .path => |p| p,5723 .path => |p| p,
...@@ -5749,7 +5764,7 @@ pub fn translateC(...@@ -5749,7 +5764,7 @@ pub fn translateC(
5749 try argv.appendSlice(comp.global_cc_argv);5764 try argv.appendSlice(comp.global_cc_argv);
5750 try argv.appendSlice(owner_mod.cc_argv);5765 try argv.appendSlice(owner_mod.cc_argv);
5751 try argv.appendSlice(&.{ source_path, "-o", translated_path });5766 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);
5753 }5768 }
57545769
5755 var stdout: []u8 = undefined;5770 var stdout: []u8 = undefined;
...@@ -5775,7 +5790,7 @@ pub fn translateC(...@@ -5775,7 +5790,7 @@ pub fn translateC(
5775 }5790 }
57765791
5777 // Just to save disk space, we delete the file because it is never needed again.5792 // 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| {
5779 log.warn("failed to delete '{s}': {t}", .{ dep_file_path, err });5794 log.warn("failed to delete '{s}': {t}", .{ dep_file_path, err });
5780 };5795 };
5781 }5796 }
...@@ -5805,7 +5820,7 @@ pub fn translateC(...@@ -5805,7 +5820,7 @@ pub fn translateC(
5805 const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest;5820 const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest;
58065821
5807 if (comp.verbose_cimport) log.info("renaming {s} to {s}", .{ tmp_sub_path, o_sub_path });5822 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
5810 return .{5825 return .{
5811 .digest = bin_digest,5826 .digest = bin_digest,
...@@ -6144,7 +6159,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6144,7 +6159,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6144 const gpa = comp.gpa;6159 const gpa = comp.gpa;
6145 const io = comp.io;6160 const io = comp.io;
61466161
6147 if (c_object.clearStatus(gpa)) {6162 if (c_object.clearStatus(gpa, io)) {
6148 // There was previous failure.6163 // There was previous failure.
6149 comp.mutex.lockUncancelable(io);6164 comp.mutex.lockUncancelable(io);
6150 defer comp.mutex.unlock(io);6165 defer comp.mutex.unlock(io);
...@@ -6257,7 +6272,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6257,7 +6272,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6257 }6272 }
62586273
6259 if (comp.verbose_cc) {6274 if (comp.verbose_cc) {
6260 dump_argv(argv.items);6275 try dumpArgv(io, argv.items);
6261 }6276 }
62626277
6263 const err = std.process.execv(arena, argv.items);6278 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...@@ -6267,8 +6282,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6267 // We can't know the digest until we do the C compiler invocation,6282 // We can't know the digest until we do the C compiler invocation,
6268 // so we need a temporary filename.6283 // so we need a temporary filename.
6269 const out_obj_path = try comp.tmpFilePath(arena, o_basename);6284 const out_obj_path = try comp.tmpFilePath(arena, o_basename);
6270 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{});6285 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{});
6271 defer zig_cache_tmp_dir.close();6286 defer zig_cache_tmp_dir.close(io);
62726287
6273 const out_diag_path = if (comp.clang_passthrough_mode or !ext.clangSupportsDiagnostics())6288 const out_diag_path = if (comp.clang_passthrough_mode or !ext.clangSupportsDiagnostics())
6274 null6289 null
...@@ -6303,15 +6318,15 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6303,15 +6318,15 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6303 }6318 }
63046319
6305 if (comp.verbose_cc) {6320 if (comp.verbose_cc) {
6306 dump_argv(argv.items);6321 try dumpArgv(io, argv.items);
6307 }6322 }
63086323
6309 // Just to save disk space, we delete the files that are never needed again.6324 // 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) {
6311 error.FileNotFound => {}, // the file wasn't created due to an error we reported6326 error.FileNotFound => {}, // the file wasn't created due to an error we reported
6312 else => log.warn("failed to delete '{s}': {s}", .{ diag_file_path, @errorName(err) }),6327 else => log.warn("failed to delete '{s}': {s}", .{ diag_file_path, @errorName(err) }),
6313 };6328 };
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) {
6315 error.FileNotFound => {}, // the file wasn't created due to an error we reported6330 error.FileNotFound => {}, // the file wasn't created due to an error we reported
6316 else => log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }),6331 else => log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }),
6317 };6332 };
...@@ -6322,7 +6337,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6322,7 +6337,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6322 child.stdout_behavior = .Inherit;6337 child.stdout_behavior = .Inherit;
6323 child.stderr_behavior = .Inherit;6338 child.stderr_behavior = .Inherit;
63246339
6325 const term = child.spawnAndWait() catch |err| {6340 const term = child.spawnAndWait(io) catch |err| {
6326 return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {s}", .{ argv.items[0], @errorName(err) });6341 return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {s}", .{ argv.items[0], @errorName(err) });
6327 };6342 };
6328 switch (term) {6343 switch (term) {
...@@ -6340,12 +6355,12 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6340,12 +6355,12 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6340 child.stdout_behavior = .Ignore;6355 child.stdout_behavior = .Ignore;
6341 child.stderr_behavior = .Pipe;6356 child.stderr_behavior = .Pipe;
63426357
6343 try child.spawn();6358 try child.spawn(io);
63446359
6345 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});6360 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
6346 const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));6361 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| {
6349 return comp.failCObj(c_object, "failed to spawn zig clang {s}: {s}", .{ argv.items[0], @errorName(err) });6364 return comp.failCObj(c_object, "failed to spawn zig clang {s}: {s}", .{ argv.items[0], @errorName(err) });
6350 };6365 };
63516366
...@@ -6387,7 +6402,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6387,7 +6402,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
63876402
6388 if (comp.file_system_inputs != null) {6403 if (comp.file_system_inputs != null) {
6389 // Use the same file size limit as the cache code does for dependency files.6404 // 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));
6391 defer gpa.free(dep_file_contents);6406 defer gpa.free(dep_file_contents);
63926407
6393 var str_buf: std.ArrayList(u8) = .empty;6408 var str_buf: std.ArrayList(u8) = .empty;
...@@ -6432,10 +6447,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6432,10 +6447,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6432 // Rename into place.6447 // Rename into place.
6433 const digest = man.final();6448 const digest = man.final();
6434 const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest });6449 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, .{});6450 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
6436 defer o_dir.close();6451 defer o_dir.close(io);
6437 const tmp_basename = fs.path.basename(out_obj_path);6452 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);
6439 break :blk digest;6454 break :blk digest;
6440 };6455 };
64416456
...@@ -6477,8 +6492,6 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -6477,8 +6492,6 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
6477 const tracy_trace = trace(@src());6492 const tracy_trace = trace(@src());
6478 defer tracy_trace.end();6493 defer tracy_trace.end();
64796494
6480 const io = comp.io;
6481
6482 const src_path = switch (win32_resource.src) {6495 const src_path = switch (win32_resource.src) {
6483 .rc => |rc_src| rc_src.src_path,6496 .rc => |rc_src| rc_src.src_path,
6484 .manifest => |src_path| src_path,6497 .manifest => |src_path| src_path,
...@@ -6487,11 +6500,13 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -6487,11 +6500,13 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
64876500
6488 log.debug("updating win32 resource: {s}", .{src_path});6501 log.debug("updating win32 resource: {s}", .{src_path});
64896502
6503 const io = comp.io;
6504
6490 var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);6505 var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
6491 defer arena_allocator.deinit();6506 defer arena_allocator.deinit();
6492 const arena = arena_allocator.allocator();6507 const arena = arena_allocator.allocator();
64936508
6494 if (win32_resource.clearStatus(comp.gpa)) {6509 if (win32_resource.clearStatus(comp.gpa, io)) {
6495 // There was previous failure.6510 // There was previous failure.
6496 comp.mutex.lockUncancelable(io);6511 comp.mutex.lockUncancelable(io);
6497 defer comp.mutex.unlock(io);6512 defer comp.mutex.unlock(io);
...@@ -6521,8 +6536,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -6521,8 +6536,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
6521 const digest = man.final();6536 const digest = man.final();
65226537
6523 const o_sub_path = try fs.path.join(arena, &.{ "o", &digest });6538 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, .{});6539 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
6525 defer o_dir.close();6540 defer o_dir.close(io);
65266541
6527 const in_rc_path = try comp.dirs.local_cache.join(comp.gpa, &.{6542 const in_rc_path = try comp.dirs.local_cache.join(comp.gpa, &.{
6528 o_sub_path, rc_basename,6543 o_sub_path, rc_basename,
...@@ -6559,7 +6574,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -6559,7 +6574,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
6559 resource_id, resource_type, fmtRcEscape(src_path),6574 resource_id, resource_type, fmtRcEscape(src_path),
6560 });6575 });
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
6564 var argv = std.array_list.Managed([]const u8).init(comp.gpa);6579 var argv = std.array_list.Managed([]const u8).init(comp.gpa);
6565 defer argv.deinit();6580 defer argv.deinit();
...@@ -6609,8 +6624,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32...@@ -6609,8 +6624,8 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
6609 const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len];6624 const rc_basename_noext = src_basename[0 .. src_basename.len - fs.path.extension(src_basename).len];
66106625
6611 const digest = if (try man.hit()) man.final() else blk: {6626 const digest = if (try man.hit()) man.final() else blk: {
6612 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{});6627 var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, "tmp", .{});
6613 defer zig_cache_tmp_dir.close();6628 defer zig_cache_tmp_dir.close(io);
66146629
6615 const res_filename = try std.fmt.allocPrint(arena, "{s}.res", .{rc_basename_noext});6630 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...@@ -6652,7 +6667,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
6652 // Read depfile and update cache manifest6667 // Read depfile and update cache manifest
6653 {6668 {
6654 const dep_basename = fs.path.basename(out_dep_path);6669 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));
6656 defer arena.free(dep_file_contents);6671 defer arena.free(dep_file_contents);
66576672
6658 const value = try std.json.parseFromSliceLeaky(std.json.Value, arena, dep_file_contents, .{});6673 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...@@ -6680,10 +6695,10 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
6680 // Rename into place.6695 // Rename into place.
6681 const digest = man.final();6696 const digest = man.final();
6682 const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest });6697 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, .{});6698 var o_dir = try comp.dirs.local_cache.handle.createDirPathOpen(io, o_sub_path, .{});
6684 defer o_dir.close();6699 defer o_dir.close(io);
6685 const tmp_basename = fs.path.basename(out_res_path);6700 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);
6687 break :blk digest;6702 break :blk digest;
6688 };6703 };
66896704
...@@ -6716,6 +6731,7 @@ fn spawnZigRc(...@@ -6716,6 +6731,7 @@ fn spawnZigRc(
6716 argv: []const []const u8,6731 argv: []const []const u8,
6717 child_progress_node: std.Progress.Node,6732 child_progress_node: std.Progress.Node,
6718) !void {6733) !void {
6734 const io = comp.io;
6719 var node_name: std.ArrayList(u8) = .empty;6735 var node_name: std.ArrayList(u8) = .empty;
6720 defer node_name.deinit(arena);6736 defer node_name.deinit(arena);
67216737
...@@ -6725,8 +6741,8 @@ fn spawnZigRc(...@@ -6725,8 +6741,8 @@ fn spawnZigRc(
6725 child.stderr_behavior = .Pipe;6741 child.stderr_behavior = .Pipe;
6726 child.progress_node = child_progress_node;6742 child.progress_node = child_progress_node;
67276743
6728 child.spawn() catch |err| {6744 child.spawn(io) catch |err| {
6729 return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv[0], @errorName(err) });6745 return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {t}", .{ argv[0], err });
6730 };6746 };
67316747
6732 var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{6748 var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{
...@@ -6758,7 +6774,7 @@ fn spawnZigRc(...@@ -6758,7 +6774,7 @@ fn spawnZigRc(
6758 // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace)6774 // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace)
6759 const stderr = poller.reader(.stderr);6775 const stderr = poller.reader(.stderr);
67606776
6761 const term = child.wait() catch |err| {6777 const term = child.wait(io) catch |err| {
6762 return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv[0], @errorName(err) });6778 return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv[0], @errorName(err) });
6763 };6779 };
67646780
...@@ -7765,17 +7781,25 @@ pub fn lockAndSetMiscFailure(...@@ -7765,17 +7781,25 @@ pub fn lockAndSetMiscFailure(
7765 return setMiscFailure(comp, tag, format, args);7781 return setMiscFailure(comp, tag, format, args);
7766}7782}
77677783
7768pub fn dump_argv(argv: []const []const u8) void {7784pub fn dumpArgv(io: Io, argv: []const []const u8) Io.Cancelable!void {
7769 var buffer: [64]u8 = undefined;7785 var buffer: [64]u8 = undefined;
7770 const stderr, _ = std.debug.lockStderrWriter(&buffer);7786 const stderr = try io.lockStderr(&buffer, null);
7771 defer std.debug.unlockStderrWriter();7787 defer io.unlockStderr();
7772 nosuspend {7788 const w = &stderr.file_writer.interface;
7773 for (argv, 0..) |arg, i| {7789 return dumpArgvWriter(w, argv) catch |err| switch (err) {
7774 if (i != 0) stderr.writeByte(' ') catch return;7790 error.WriteFailed => switch (stderr.file_writer.err.?) {
7775 stderr.writeAll(arg) catch return;7791 error.Canceled => return error.Canceled,
7776 }7792 else => return,
7777 stderr.writeByte('\n') catch 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);
7778 }7801 }
7802 try w.writeByte('\n');
7779}7803}
77807804
7781pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend {7805pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend {
src/InternPool.zig+30-30
...@@ -1,20 +1,21 @@...@@ -1,20 +1,21 @@
1//! All interned objects have both a value and a type.1//! All interned objects have both a value and a type.
2//! This data structure is self-contained.2//! This data structure is self-contained.
3const InternPool = @This();
34
4const builtin = @import("builtin");5const builtin = @import("builtin");
6
5const std = @import("std");7const std = @import("std");
8const Io = std.Io;
6const Allocator = std.mem.Allocator;9const Allocator = std.mem.Allocator;
7const assert = std.debug.assert;10const assert = std.debug.assert;
8const BigIntConst = std.math.big.int.Const;11const BigIntConst = std.math.big.int.Const;
9const BigIntMutable = std.math.big.int.Mutable;12const BigIntMutable = std.math.big.int.Mutable;
10const Cache = std.Build.Cache;13const Cache = std.Build.Cache;
11const Io = std.Io;
12const Limb = std.math.big.Limb;14const Limb = std.math.big.Limb;
13const Hash = std.hash.Wyhash;15const Hash = std.hash.Wyhash;
16const Zir = std.zig.Zir;
1417
15const InternPool = @This();
16const Zcu = @import("Zcu.zig");18const Zcu = @import("Zcu.zig");
17const Zir = std.zig.Zir;
1819
19/// One item per thread, indexed by `tid`, which is dense and unique per thread.20/// One item per thread, indexed by `tid`, which is dense and unique per thread.
20locals: []Local,21locals: []Local,
...@@ -11166,11 +11167,15 @@ pub fn mutateVarInit(ip: *InternPool, io: Io, index: Index, init_index: Index) v...@@ -11166,11 +11167,15 @@ pub fn mutateVarInit(ip: *InternPool, io: Io, index: Index, init_index: Index) v
11166}11167}
1116711168
11168pub fn dump(ip: *const InternPool) void {11169pub fn dump(ip: *const InternPool) void {
11169 dumpStatsFallible(ip, std.heap.page_allocator) catch return;11170 var buffer: [4096]u8 = undefined;
11170 dumpAllFallible(ip) catch return;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;
11171}11176}
1117211177
11173fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {11178fn dumpStatsFallible(ip: *const InternPool, w: *Io.Writer, arena: Allocator) !void {
11174 var items_len: usize = 0;11179 var items_len: usize = 0;
11175 var extra_len: usize = 0;11180 var extra_len: usize = 0;
11176 var limbs_len: usize = 0;11181 var limbs_len: usize = 0;
...@@ -11423,18 +11428,13 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {...@@ -11423,18 +11428,13 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {
11423 };11428 };
11424 counts.sort(SortContext{ .map = &counts });11429 counts.sort(SortContext{ .map = &counts });
11425 const len = @min(50, counts.count());11430 const len = @min(50, counts.count());
11426 std.debug.print(" top 50 tags:\n", .{});11431 try w.print(" top 50 tags:\n", .{});
11427 for (counts.keys()[0..len], counts.values()[0..len]) |tag, stats| {11432 for (counts.keys()[0..len], counts.values()[0..len]) |tag, stats| {
11428 std.debug.print(" {s}: {d} occurrences, {d} total bytes\n", .{11433 try w.print(" {t}: {d} occurrences, {d} total bytes\n", .{ tag, stats.count, stats.bytes });
11429 @tagName(tag), stats.count, stats.bytes,
11430 });
11431 }11434 }
11432}11435}
1143311436
11434fn dumpAllFallible(ip: *const InternPool) anyerror!void {11437fn dumpAllFallible(ip: *const InternPool, w: *Io.Writer) anyerror!void {
11435 var buffer: [4096]u8 = undefined;
11436 const stderr_bw, _ = std.debug.lockStderrWriter(&buffer);
11437 defer std.debug.unlockStderrWriter();
11438 for (ip.locals, 0..) |*local, tid| {11438 for (ip.locals, 0..) |*local, tid| {
11439 const items = local.shared.items.view();11439 const items = local.shared.items.view();
11440 for (11440 for (
...@@ -11443,12 +11443,12 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void {...@@ -11443,12 +11443,12 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void {
11443 0..,11443 0..,
11444 ) |tag, data, index| {11444 ) |tag, data, index| {
11445 const i = Index.Unwrapped.wrap(.{ .tid = @enumFromInt(tid), .index = @intCast(index) }, ip);11445 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) });
11447 switch (tag) {11447 switch (tag) {
11448 .removed => {},11448 .removed => {},
1144911449
11450 .simple_type => try stderr_bw.print("{s}", .{@tagName(@as(SimpleType, @enumFromInt(@intFromEnum(i))))}),11450 .simple_type => try w.print("{s}", .{@tagName(@as(SimpleType, @enumFromInt(@intFromEnum(i))))}),
11451 .simple_value => try stderr_bw.print("{s}", .{@tagName(@as(SimpleValue, @enumFromInt(@intFromEnum(i))))}),11451 .simple_value => try w.print("{s}", .{@tagName(@as(SimpleValue, @enumFromInt(@intFromEnum(i))))}),
1145211452
11453 .type_int_signed,11453 .type_int_signed,
11454 .type_int_unsigned,11454 .type_int_unsigned,
...@@ -11521,23 +11521,27 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void {...@@ -11521,23 +11521,27 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void {
11521 .func_coerced,11521 .func_coerced,
11522 .union_value,11522 .union_value,
11523 .memoized_call,11523 .memoized_call,
11524 => try stderr_bw.print("{d}", .{data}),11524 => try w.print("{d}", .{data}),
1152511525
11526 .opt_null,11526 .opt_null,
11527 .type_slice,11527 .type_slice,
11528 .only_possible_value,11528 .only_possible_value,
11529 => try stderr_bw.print("${d}", .{data}),11529 => try w.print("${d}", .{data}),
11530 }11530 }
11531 try stderr_bw.writeAll(")\n");11531 try w.writeAll(")\n");
11532 }11532 }
11533 }11533 }
11534}11534}
1153511535
11536pub fn dumpGenericInstances(ip: *const InternPool, allocator: Allocator) void {11536pub 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;
11538}11542}
1153911543
11540pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) anyerror!void {11544pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator, w: *Io.Writer) !void {
11541 var arena_allocator = std.heap.ArenaAllocator.init(allocator);11545 var arena_allocator = std.heap.ArenaAllocator.init(allocator);
11542 defer arena_allocator.deinit();11546 defer arena_allocator.deinit();
11543 const arena = arena_allocator.allocator();11547 const arena = arena_allocator.allocator();
...@@ -11564,10 +11568,6 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator)...@@ -11564,10 +11568,6 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator)
11564 }11568 }
11565 }11569 }
1156611570
11567 var buffer: [4096]u8 = undefined;
11568 const stderr_bw, _ = std.debug.lockStderrWriter(&buffer);
11569 defer std.debug.unlockStderrWriter();
11570
11571 const SortContext = struct {11571 const SortContext = struct {
11572 values: []std.ArrayList(Index),11572 values: []std.ArrayList(Index),
11573 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {11573 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
...@@ -11579,19 +11579,19 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator)...@@ -11579,19 +11579,19 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator)
11579 var it = instances.iterator();11579 var it = instances.iterator();
11580 while (it.next()) |entry| {11580 while (it.next()) |entry| {
11581 const generic_fn_owner_nav = ip.getNav(ip.funcDeclInfo(entry.key_ptr.*).owner_nav);11581 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 });
11583 for (entry.value_ptr.items) |index| {11583 for (entry.value_ptr.items) |index| {
11584 const unwrapped_index = index.unwrap(ip);11584 const unwrapped_index = index.unwrap(ip);
11585 const func = ip.extraFuncInstance(unwrapped_index.tid, unwrapped_index.getExtra(ip), unwrapped_index.getData(ip));11585 const func = ip.extraFuncInstance(unwrapped_index.tid, unwrapped_index.getExtra(ip), unwrapped_index.getData(ip));
11586 const owner_nav = ip.getNav(func.owner_nav);11586 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)});
11588 for (func.comptime_args.get(ip)) |arg| {11588 for (func.comptime_args.get(ip)) |arg| {
11589 if (arg != .none) {11589 if (arg != .none) {
11590 const key = ip.indexToKey(arg);11590 const key = ip.indexToKey(arg);
11591 try stderr_bw.print(" {} ", .{key});11591 try w.print(" {} ", .{key});
11592 }11592 }
11593 }11593 }
11594 try stderr_bw.writeAll(")\n");11594 try w.writeAll(")\n");
11595 }11595 }
11596 }11596 }
11597}11597}
src/Package/Fetch.zig+125-121
...@@ -383,14 +383,14 @@ pub fn run(f: *Fetch) RunError!void {...@@ -383,14 +383,14 @@ pub fn run(f: *Fetch) RunError!void {
383 },383 },
384 .remote => |remote| remote,384 .remote => |remote| remote,
385 .path_or_url => |path_or_url| {385 .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| {
387 var resource: Resource = .{ .dir = dir };387 var resource: Resource = .{ .dir = dir };
388 return f.runResource(path_or_url, &resource, null);388 return f.runResource(path_or_url, &resource, null);
389 } else |dir_err| {389 } else |dir_err| {
390 var server_header_buffer: [init_resource_buffer_size]u8 = undefined;390 var server_header_buffer: [init_resource_buffer_size]u8 = undefined;
391391
392 const file_err = if (dir_err == error.NotDir) e: {392 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| {
394 var resource: Resource = .{ .file = file.reader(io, &server_header_buffer) };394 var resource: Resource = .{ .file = file.reader(io, &server_header_buffer) };
395 return f.runResource(path_or_url, &resource, null);395 return f.runResource(path_or_url, &resource, null);
396 } else |err| break :e err;396 } else |err| break :e err;
...@@ -418,7 +418,7 @@ pub fn run(f: *Fetch) RunError!void {...@@ -418,7 +418,7 @@ pub fn run(f: *Fetch) RunError!void {
418 const prefixed_pkg_sub_path = prefixed_pkg_sub_path_buffer[0 .. 2 + hash_slice.len];418 const prefixed_pkg_sub_path = prefixed_pkg_sub_path_buffer[0 .. 2 + hash_slice.len];
419 const prefix_len: usize = if (f.job_queue.read_only) "p/".len else 0;419 const prefix_len: usize = if (f.job_queue.read_only) "p/".len else 0;
420 const pkg_sub_path = prefixed_pkg_sub_path[prefix_len..];420 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, .{})) |_| {
422 assert(f.lazy_status != .unavailable);422 assert(f.lazy_status != .unavailable);
423 f.package_root = .{423 f.package_root = .{
424 .root_dir = cache_root,424 .root_dir = cache_root,
...@@ -500,12 +500,12 @@ fn runResource(...@@ -500,12 +500,12 @@ fn runResource(
500 var tmp_directory: Cache.Directory = .{500 var tmp_directory: Cache.Directory = .{
501 .path = tmp_directory_path,501 .path = tmp_directory_path,
502 .handle = handle: {502 .handle = handle: {
503 const dir = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{503 const dir = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{
504 .iterate = true,504 .open_options = .{ .iterate = true },
505 }) catch |err| {505 }) catch |err| {
506 try eb.addRootErrorMessage(.{506 try eb.addRootErrorMessage(.{
507 .msg = try eb.printString("unable to create temporary directory '{s}': {s}", .{507 .msg = try eb.printString("unable to create temporary directory '{s}': {t}", .{
508 tmp_directory_path, @errorName(err),508 tmp_directory_path, err,
509 }),509 }),
510 });510 });
511 return error.FetchFailed;511 return error.FetchFailed;
...@@ -513,7 +513,7 @@ fn runResource(...@@ -513,7 +513,7 @@ fn runResource(
513 break :handle dir;513 break :handle dir;
514 },514 },
515 };515 };
516 defer tmp_directory.handle.close();516 defer tmp_directory.handle.close(io);
517517
518 // Fetch and unpack a resource into a temporary directory.518 // Fetch and unpack a resource into a temporary directory.
519 var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory);519 var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory);
...@@ -523,9 +523,9 @@ fn runResource(...@@ -523,9 +523,9 @@ fn runResource(
523 // Apply btrfs workaround if needed. Reopen tmp_directory.523 // Apply btrfs workaround if needed. Reopen tmp_directory.
524 if (native_os == .linux and f.job_queue.work_around_btrfs_bug) {524 if (native_os == .linux and f.job_queue.work_around_btrfs_bug) {
525 // https://github.com/ziglang/zig/issues/17095525 // https://github.com/ziglang/zig/issues/17095
526 pkg_path.root_dir.handle.close();526 pkg_path.root_dir.handle.close(io);
527 pkg_path.root_dir.handle = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{527 pkg_path.root_dir.handle = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{
528 .iterate = true,528 .open_options = .{ .iterate = true },
529 }) catch @panic("btrfs workaround failed");529 }) catch @panic("btrfs workaround failed");
530 }530 }
531531
...@@ -567,7 +567,7 @@ fn runResource(...@@ -567,7 +567,7 @@ fn runResource(
567 .root_dir = cache_root,567 .root_dir = cache_root,
568 .sub_path = try std.fmt.allocPrint(arena, "p" ++ s ++ "{s}", .{computed_package_hash.toSlice()}),568 .sub_path = try std.fmt.allocPrint(arena, "p" ++ s ++ "{s}", .{computed_package_hash.toSlice()}),
569 };569 };
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| {
571 const src = try cache_root.join(arena, &.{tmp_dir_sub_path});571 const src = try cache_root.join(arena, &.{tmp_dir_sub_path});
572 const dest = try cache_root.join(arena, &.{f.package_root.sub_path});572 const dest = try cache_root.join(arena, &.{f.package_root.sub_path});
573 try eb.addRootErrorMessage(.{ .msg = try eb.printString(573 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
...@@ -578,7 +578,7 @@ fn runResource(...@@ -578,7 +578,7 @@ fn runResource(
578 };578 };
579 // Remove temporary directory root if not already renamed to global cache.579 // Remove temporary directory root if not already renamed to global cache.
580 if (!std.mem.eql(u8, package_sub_path, tmp_dir_sub_path)) {580 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 {};
582 }582 }
583583
584 // Validate the computed hash against the expected hash. If invalid, this584 // Validate the computed hash against the expected hash. If invalid, this
...@@ -637,8 +637,9 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash {...@@ -637,8 +637,9 @@ pub fn computedPackageHash(f: *const Fetch) Package.Hash {
637/// `computeHash` gets a free check for the existence of `build.zig`, but when637/// `computeHash` gets a free check for the existence of `build.zig`, but when
638/// not computing a hash, we need to do a syscall to check for it.638/// not computing a hash, we need to do a syscall to check for it.
639fn checkBuildFileExistence(f: *Fetch) RunError!void {639fn checkBuildFileExistence(f: *Fetch) RunError!void {
640 const io = f.job_queue.io;
640 const eb = &f.error_bundle;641 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, .{})) |_| {
642 f.has_build_zig = true;643 f.has_build_zig = true;
643 } else |err| switch (err) {644 } else |err| switch (err) {
644 error.FileNotFound => {},645 error.FileNotFound => {},
...@@ -655,9 +656,11 @@ fn checkBuildFileExistence(f: *Fetch) RunError!void {...@@ -655,9 +656,11 @@ fn checkBuildFileExistence(f: *Fetch) RunError!void {
655656
656/// This function populates `f.manifest` or leaves it `null`.657/// This function populates `f.manifest` or leaves it `null`.
657fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {658fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {
659 const io = f.job_queue.io;
658 const eb = &f.error_bundle;660 const eb = &f.error_bundle;
659 const arena = f.arena.allocator();661 const arena = f.arena.allocator();
660 const manifest_bytes = pkg_root.root_dir.handle.readFileAllocOptions(662 const manifest_bytes = pkg_root.root_dir.handle.readFileAllocOptions(
663 io,
661 try fs.path.join(arena, &.{ pkg_root.sub_path, Manifest.basename }),664 try fs.path.join(arena, &.{ pkg_root.sub_path, Manifest.basename }),
662 arena,665 arena,
663 .limited(Manifest.max_bytes),666 .limited(Manifest.max_bytes),
...@@ -882,10 +885,10 @@ fn fail(f: *Fetch, msg_tok: std.zig.Ast.TokenIndex, msg_str: u32) RunError {...@@ -882,10 +885,10 @@ fn fail(f: *Fetch, msg_tok: std.zig.Ast.TokenIndex, msg_str: u32) RunError {
882}885}
883886
884const Resource = union(enum) {887const Resource = union(enum) {
885 file: fs.File.Reader,888 file: Io.File.Reader,
886 http_request: HttpRequest,889 http_request: HttpRequest,
887 git: Git,890 git: Git,
888 dir: fs.Dir,891 dir: Io.Dir,
889892
890 const Git = struct {893 const Git = struct {
891 session: git.Session,894 session: git.Session,
...@@ -908,7 +911,7 @@ const Resource = union(enum) {...@@ -908,7 +911,7 @@ const Resource = union(enum) {
908 .git => |*git_resource| {911 .git => |*git_resource| {
909 git_resource.fetch_stream.deinit();912 git_resource.fetch_stream.deinit();
910 },913 },
911 .dir => |*dir| dir.close(),914 .dir => |*dir| dir.close(io),
912 }915 }
913 resource.* = undefined;916 resource.* = undefined;
914 }917 }
...@@ -995,7 +998,7 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u...@@ -995,7 +998,7 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u
995998
996 if (ascii.eqlIgnoreCase(uri.scheme, "file")) {999 if (ascii.eqlIgnoreCase(uri.scheme, "file")) {
997 const path = try uri.path.toRawMaybeAlloc(arena);1000 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| {
999 return f.fail(f.location_tok, try eb.printString("unable to open '{f}{s}': {t}", .{1002 return f.fail(f.location_tok, try eb.printString("unable to open '{f}{s}': {t}", .{
1000 f.parent_package_root, path, err,1003 f.parent_package_root, path, err,
1001 }));1004 }));
...@@ -1247,13 +1250,14 @@ fn unpackResource(...@@ -1247,13 +1250,14 @@ fn unpackResource(
1247 }1250 }
1248}1251}
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 {
1251 const eb = &f.error_bundle;1254 const eb = &f.error_bundle;
1252 const arena = f.arena.allocator();1255 const arena = f.arena.allocator();
1256 const io = f.job_queue.io;
12531257
1254 var diagnostics: std.tar.Diagnostics = .{ .allocator = arena };1258 var diagnostics: std.tar.Diagnostics = .{ .allocator = arena };
12551259
1256 std.tar.pipeToFileSystem(out_dir, reader, .{1260 std.tar.pipeToFileSystem(io, out_dir, reader, .{
1257 .diagnostics = &diagnostics,1261 .diagnostics = &diagnostics,
1258 .strip_components = 0,1262 .strip_components = 0,
1259 .mode_mode = .ignore,1263 .mode_mode = .ignore,
...@@ -1280,7 +1284,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) RunError!Unpack...@@ -1280,7 +1284,7 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) RunError!Unpack
12801284
1281fn unzip(1285fn unzip(
1282 f: *Fetch,1286 f: *Fetch,
1283 out_dir: fs.Dir,1287 out_dir: Io.Dir,
1284 reader: *Io.Reader,1288 reader: *Io.Reader,
1285) error{ ReadFailed, OutOfMemory, Canceled, FetchFailed }!UnpackResult {1289) error{ ReadFailed, OutOfMemory, Canceled, FetchFailed }!UnpackResult {
1286 // We write the entire contents to a file first because zip files1290 // We write the entire contents to a file first because zip files
...@@ -1302,7 +1306,7 @@ fn unzip(...@@ -1302,7 +1306,7 @@ fn unzip(
1302 const random_integer = std.crypto.random.int(u64);1306 const random_integer = std.crypto.random.int(u64);
1303 zip_path[prefix.len..][0..random_len].* = std.fmt.hex(random_integer);1307 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, .{
1306 .exclusive = true,1310 .exclusive = true,
1307 .read = true,1311 .read = true,
1308 }) catch |err| switch (err) {1312 }) catch |err| switch (err) {
...@@ -1314,10 +1318,10 @@ fn unzip(...@@ -1314,10 +1318,10 @@ fn unzip(
1314 ),1318 ),
1315 };1319 };
1316 };1320 };
1317 defer zip_file.close();1321 defer zip_file.close(io);
1318 var zip_file_buffer: [4096]u8 = undefined;1322 var zip_file_buffer: [4096]u8 = undefined;
1319 var zip_file_reader = b: {1323 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
1322 _ = reader.streamRemaining(&zip_file_writer.interface) catch |err| switch (err) {1326 _ = reader.streamRemaining(&zip_file_writer.interface) catch |err| switch (err) {
1323 error.ReadFailed => return error.ReadFailed,1327 error.ReadFailed => return error.ReadFailed,
...@@ -1330,7 +1334,7 @@ fn unzip(...@@ -1330,7 +1334,7 @@ fn unzip(
1330 f.location_tok,1334 f.location_tok,
1331 try eb.printString("failed writing temporary zip file: {t}", .{err}),1335 try eb.printString("failed writing temporary zip file: {t}", .{err}),
1332 );1336 );
1333 break :b zip_file_writer.moveToReader(io);1337 break :b zip_file_writer.moveToReader();
1334 };1338 };
13351339
1336 var diagnostics: std.zip.Diagnostics = .{ .allocator = f.arena.allocator() };1340 var diagnostics: std.zip.Diagnostics = .{ .allocator = f.arena.allocator() };
...@@ -1343,13 +1347,13 @@ fn unzip(...@@ -1343,13 +1347,13 @@ fn unzip(
1343 .diagnostics = &diagnostics,1347 .diagnostics = &diagnostics,
1344 }) catch |err| return f.fail(f.location_tok, try eb.printString("zip extract failed: {t}", .{err}));1348 }) 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|
1347 return f.fail(f.location_tok, try eb.printString("delete temporary zip failed: {t}", .{err}));1351 return f.fail(f.location_tok, try eb.printString("delete temporary zip failed: {t}", .{err}));
13481352
1349 return .{ .root_dir = diagnostics.root_dir };1353 return .{ .root_dir = diagnostics.root_dir };
1350}1354}
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 {
1353 const io = f.job_queue.io;1357 const io = f.job_queue.io;
1354 const arena = f.arena.allocator();1358 const arena = f.arena.allocator();
1355 // TODO don't try to get a gpa from an arena. expose this dependency higher up1359 // 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...@@ -1362,23 +1366,23 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
1362 // we do not attempt to replicate the exact structure of a real .git1366 // we do not attempt to replicate the exact structure of a real .git
1363 // directory, since that isn't relevant for fetching a package.1367 // directory, since that isn't relevant for fetching a package.
1364 {1368 {
1365 var pack_dir = try out_dir.makeOpenPath(".git", .{});1369 var pack_dir = try out_dir.createDirPathOpen(io, ".git", .{});
1366 defer pack_dir.close();1370 defer pack_dir.close(io);
1367 var pack_file = try pack_dir.createFile("pkg.pack", .{ .read = true });1371 var pack_file = try pack_dir.createFile(io, "pkg.pack", .{ .read = true });
1368 defer pack_file.close();1372 defer pack_file.close(io);
1369 var pack_file_buffer: [4096]u8 = undefined;1373 var pack_file_buffer: [4096]u8 = undefined;
1370 var pack_file_reader = b: {1374 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);
1372 const fetch_reader = &resource.fetch_stream.reader;1376 const fetch_reader = &resource.fetch_stream.reader;
1373 _ = try fetch_reader.streamRemaining(&pack_file_writer.interface);1377 _ = try fetch_reader.streamRemaining(&pack_file_writer.interface);
1374 try pack_file_writer.interface.flush();1378 try pack_file_writer.interface.flush();
1375 break :b pack_file_writer.moveToReader(io);1379 break :b pack_file_writer.moveToReader();
1376 };1380 };
13771381
1378 var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true });1382 var index_file = try pack_dir.createFile(io, "pkg.idx", .{ .read = true });
1379 defer index_file.close();1383 defer index_file.close(io);
1380 var index_file_buffer: [2000]u8 = undefined;1384 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);
1382 {1386 {
1383 const index_prog_node = f.prog_node.start("Index pack", 0);1387 const index_prog_node = f.prog_node.start("Index pack", 0);
1384 defer index_prog_node.end();1388 defer index_prog_node.end();
...@@ -1393,7 +1397,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U...@@ -1393,7 +1397,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
1393 try repository.init(gpa, object_format, &pack_file_reader, &index_file_reader);1397 try repository.init(gpa, object_format, &pack_file_reader, &index_file_reader);
1394 defer repository.deinit();1398 defer repository.deinit();
1395 var diagnostics: git.Diagnostics = .{ .allocator = arena };1399 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
1398 if (diagnostics.errors.items.len > 0) {1402 if (diagnostics.errors.items.len > 0) {
1399 try res.allocErrors(arena, diagnostics.errors.items.len, "unable to unpack packfile");1403 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...@@ -1407,41 +1411,37 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
1407 }1411 }
1408 }1412 }
14091413
1410 try out_dir.deleteTree(".git");1414 try out_dir.deleteTree(io, ".git");
1411 return res;1415 return res;
1412}1416}
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 {
1415 const gpa = f.arena.child_allocator;1419 const gpa = f.arena.child_allocator;
1420 const io = f.job_queue.io;
1416 // Recursive directory copy.1421 // Recursive directory copy.
1417 var it = try dir.walk(gpa);1422 var it = try dir.walk(gpa);
1418 defer it.deinit();1423 defer it.deinit();
1419 while (try it.next()) |entry| {1424 while (try it.next(io)) |entry| {
1420 switch (entry.kind) {1425 switch (entry.kind) {
1421 .directory => {}, // omit empty directories1426 .directory => {}, // omit empty directories
1422 .file => {1427 .file => {
1423 dir.copyFile(1428 dir.copyFile(entry.path, tmp_dir, entry.path, io, .{}) catch |err| switch (err) {
1424 entry.path,
1425 tmp_dir,
1426 entry.path,
1427 .{},
1428 ) catch |err| switch (err) {
1429 error.FileNotFound => {1429 error.FileNotFound => {
1430 if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname);1430 if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.createDirPath(io, dirname);
1431 try dir.copyFile(entry.path, tmp_dir, entry.path, .{});1431 try dir.copyFile(entry.path, tmp_dir, entry.path, io, .{});
1432 },1432 },
1433 else => |e| return e,1433 else => |e| return e,
1434 };1434 };
1435 },1435 },
1436 .sym_link => {1436 .sym_link => {
1437 var buf: [fs.max_path_bytes]u8 = undefined;1437 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)];
1439 // TODO: if this would create a symlink to outside1439 // TODO: if this would create a symlink to outside
1440 // the destination directory, fail with an error instead.1440 // 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) {
1442 error.FileNotFound => {1442 error.FileNotFound => {
1443 if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname);1443 if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.createDirPath(io, dirname);
1444 try tmp_dir.symLink(link_name, entry.path, .{});1444 try tmp_dir.symLink(io, link_name, entry.path, .{});
1445 },1445 },
1446 else => |e| return e,1446 else => |e| return e,
1447 };1447 };
...@@ -1451,14 +1451,14 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void...@@ -1451,14 +1451,14 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void
1451 }1451 }
1452}1452}
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 {
1455 assert(dest_dir_sub_path[1] == fs.path.sep);1455 assert(dest_dir_sub_path[1] == fs.path.sep);
1456 var handled_missing_dir = false;1456 var handled_missing_dir = false;
1457 while (true) {1457 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) {
1459 error.FileNotFound => {1459 error.FileNotFound => {
1460 if (handled_missing_dir) return err;1460 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) {
1462 error.PathAlreadyExists => handled_missing_dir = true,1462 error.PathAlreadyExists => handled_missing_dir = true,
1463 else => |e| return e,1463 else => |e| return e,
1464 };1464 };
...@@ -1466,7 +1466,7 @@ pub fn renameTmpIntoCache(cache_dir: fs.Dir, tmp_dir_sub_path: []const u8, dest_...@@ -1466,7 +1466,7 @@ pub fn renameTmpIntoCache(cache_dir: fs.Dir, tmp_dir_sub_path: []const u8, dest_
1466 },1466 },
1467 error.PathAlreadyExists, error.AccessDenied => {1467 error.PathAlreadyExists, error.AccessDenied => {
1468 // Package has been already downloaded and may already be in use on the system.1468 // 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 {
1470 // Garbage files leftover in zig-cache/tmp/ is, as they say1470 // Garbage files leftover in zig-cache/tmp/ is, as they say
1471 // on Star Trek, "operating within normal parameters".1471 // on Star Trek, "operating within normal parameters".
1472 };1472 };
...@@ -1519,7 +1519,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1519,7 +1519,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1519 var group: Io.Group = .init;1519 var group: Io.Group = .init;
1520 defer group.wait(io);1520 defer group.wait(io);
15211521
1522 while (walker.next() catch |err| {1522 while (walker.next(io) catch |err| {
1523 try eb.addRootErrorMessage(.{ .msg = try eb.printString(1523 try eb.addRootErrorMessage(.{ .msg = try eb.printString(
1524 "unable to walk temporary directory '{f}': {s}",1524 "unable to walk temporary directory '{f}': {s}",
1525 .{ pkg_path, @errorName(err) },1525 .{ pkg_path, @errorName(err) },
...@@ -1542,7 +1542,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1542,7 +1542,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1542 .fs_path = fs_path,1542 .fs_path = fs_path,
1543 .failure = undefined, // to be populated by the worker1543 .failure = undefined, // to be populated by the worker
1544 };1544 };
1545 group.async(io, workerDeleteFile, .{ root_dir, deleted_file });1545 group.async(io, workerDeleteFile, .{ io, root_dir, deleted_file });
1546 try deleted_files.append(deleted_file);1546 try deleted_files.append(deleted_file);
1547 continue;1547 continue;
1548 }1548 }
...@@ -1570,7 +1570,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1570,7 +1570,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1570 .failure = undefined, // to be populated by the worker1570 .failure = undefined, // to be populated by the worker
1571 .size = undefined, // to be populated by the worker1571 .size = undefined, // to be populated by the worker
1572 };1572 };
1573 group.async(io, workerHashFile, .{ root_dir, hashed_file });1573 group.async(io, workerHashFile, .{ io, root_dir, hashed_file });
1574 try all_files.append(hashed_file);1574 try all_files.append(hashed_file);
1575 }1575 }
1576 }1576 }
...@@ -1588,7 +1588,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1588,7 +1588,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1588 var i: usize = 0;1588 var i: usize = 0;
1589 while (i < sus_dirs.count()) : (i += 1) {1589 while (i < sus_dirs.count()) : (i += 1) {
1590 const sus_dir = sus_dirs.keys()[i];1590 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) {
1592 error.DirNotEmpty => continue,1592 error.DirNotEmpty => continue,
1593 error.FileNotFound => continue,1593 error.FileNotFound => continue,
1594 else => |e| {1594 else => |e| {
...@@ -1638,7 +1638,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1638,7 +1638,7 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1638 assert(!f.job_queue.recursive);1638 assert(!f.job_queue.recursive);
1639 // Print something to stdout that can be text diffed to figure out why1639 // Print something to stdout that can be text diffed to figure out why
1640 // the package hash is different.1640 // the package hash is different.
1641 dumpHashInfo(all_files.items) catch |err| {1641 dumpHashInfo(io, all_files.items) catch |err| {
1642 std.debug.print("unable to write to stdout: {s}\n", .{@errorName(err)});1642 std.debug.print("unable to write to stdout: {s}\n", .{@errorName(err)});
1643 std.process.exit(1);1643 std.process.exit(1);
1644 };1644 };
...@@ -1650,9 +1650,9 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute...@@ -1650,9 +1650,9 @@ fn computeHash(f: *Fetch, pkg_path: Cache.Path, filter: Filter) RunError!Compute
1650 };1650 };
1651}1651}
16521652
1653fn dumpHashInfo(all_files: []const *const HashedFile) !void {1653fn dumpHashInfo(io: Io, all_files: []const *const HashedFile) !void {
1654 var stdout_buffer: [1024]u8 = undefined;1654 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);
1656 const w = &stdout_writer.interface;1656 const w = &stdout_writer.interface;
1657 for (all_files) |hashed_file| {1657 for (all_files) |hashed_file| {
1658 try w.print("{t}: {x}: {s}\n", .{ hashed_file.kind, &hashed_file.hash, hashed_file.normalized_path });1658 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 {...@@ -1660,15 +1660,15 @@ fn dumpHashInfo(all_files: []const *const HashedFile) !void {
1660 try w.flush();1660 try w.flush();
1661}1661}
16621662
1663fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile) void {1663fn workerHashFile(io: Io, dir: Io.Dir, hashed_file: *HashedFile) void {
1664 hashed_file.failure = hashFileFallible(dir, hashed_file);1664 hashed_file.failure = hashFileFallible(io, dir, hashed_file);
1665}1665}
16661666
1667fn workerDeleteFile(dir: fs.Dir, deleted_file: *DeletedFile) void {1667fn workerDeleteFile(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) void {
1668 deleted_file.failure = deleteFileFallible(dir, deleted_file);1668 deleted_file.failure = deleteFileFallible(io, dir, deleted_file);
1669}1669}
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 {
1672 var buf: [8000]u8 = undefined;1672 var buf: [8000]u8 = undefined;
1673 var hasher = Package.Hash.Algo.init(.{});1673 var hasher = Package.Hash.Algo.init(.{});
1674 hasher.update(hashed_file.normalized_path);1674 hasher.update(hashed_file.normalized_path);
...@@ -1676,24 +1676,24 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void...@@ -1676,24 +1676,24 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void
16761676
1677 switch (hashed_file.kind) {1677 switch (hashed_file.kind) {
1678 .file => {1678 .file => {
1679 var file = try dir.openFile(hashed_file.fs_path, .{});1679 var file = try dir.openFile(io, hashed_file.fs_path, .{});
1680 defer file.close();1680 defer file.close(io);
1681 // Hard-coded false executable bit: https://github.com/ziglang/zig/issues/174631681 // Hard-coded false executable bit: https://github.com/ziglang/zig/issues/17463
1682 hasher.update(&.{ 0, 0 });1682 hasher.update(&.{ 0, 0 });
1683 var file_header: FileHeader = .{};1683 var file_header: FileHeader = .{};
1684 while (true) {1684 while (true) {
1685 const bytes_read = try file.read(&buf);1685 const bytes_read = try file.readPositional(io, &.{&buf}, file_size);
1686 if (bytes_read == 0) break;1686 if (bytes_read == 0) break;
1687 file_size += bytes_read;1687 file_size += bytes_read;
1688 hasher.update(buf[0..bytes_read]);1688 hasher.update(buf[0..bytes_read]);
1689 file_header.update(buf[0..bytes_read]);1689 file_header.update(buf[0..bytes_read]);
1690 }1690 }
1691 if (file_header.isExecutable()) {1691 if (file_header.isExecutable()) {
1692 try setExecutable(file);1692 try setExecutable(io, file);
1693 }1693 }
1694 },1694 },
1695 .link => {1695 .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)];
1697 if (fs.path.sep != canonical_sep) {1697 if (fs.path.sep != canonical_sep) {
1698 // Package hashes are intended to be consistent across1698 // Package hashes are intended to be consistent across
1699 // platforms which means we must normalize path separators1699 // platforms which means we must normalize path separators
...@@ -1707,16 +1707,13 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void...@@ -1707,16 +1707,13 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void
1707 hashed_file.size = file_size;1707 hashed_file.size = file_size;
1708}1708}
17091709
1710fn deleteFileFallible(dir: fs.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void {1710fn deleteFileFallible(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void {
1711 try dir.deleteFile(deleted_file.fs_path);1711 try dir.deleteFile(io, deleted_file.fs_path);
1712}1712}
17131713
1714fn setExecutable(file: fs.File) !void {1714fn setExecutable(io: Io, file: Io.File) !void {
1715 if (!std.fs.has_executable_bit) return;1715 if (!Io.File.Permissions.has_executable_bit) return;
17161716 try file.setPermissions(io, .executable_file);
1717 const S = std.posix.S;
1718 const mode = fs.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH;
1719 try file.chmod(mode);
1720}1717}
17211718
1722const DeletedFile = struct {1719const DeletedFile = struct {
...@@ -1724,8 +1721,8 @@ const DeletedFile = struct {...@@ -1724,8 +1721,8 @@ const DeletedFile = struct {
1724 failure: Error!void,1721 failure: Error!void,
17251722
1726 const Error =1723 const Error =
1727 fs.Dir.DeleteFileError ||1724 Io.Dir.DeleteFileError ||
1728 fs.Dir.DeleteDirError;1725 Io.Dir.DeleteDirError;
1729};1726};
17301727
1731const HashedFile = struct {1728const HashedFile = struct {
...@@ -1737,11 +1734,11 @@ const HashedFile = struct {...@@ -1737,11 +1734,11 @@ const HashedFile = struct {
1737 size: u64,1734 size: u64,
17381735
1739 const Error =1736 const Error =
1740 fs.File.OpenError ||1737 Io.File.OpenError ||
1741 fs.File.ReadError ||1738 Io.File.ReadPositionalError ||
1742 fs.File.StatError ||1739 Io.File.StatError ||
1743 fs.File.ChmodError ||1740 Io.File.SetPermissionsError ||
1744 fs.Dir.ReadLinkError;1741 Io.Dir.ReadLinkError;
17451742
1746 const Kind = enum { file, link };1743 const Kind = enum { file, link };
17471744
...@@ -2043,7 +2040,7 @@ const UnpackResult = struct {...@@ -2043,7 +2040,7 @@ const UnpackResult = struct {
2043 defer errors.deinit(gpa);2040 defer errors.deinit(gpa);
2044 var aw: Io.Writer.Allocating = .init(gpa);2041 var aw: Io.Writer.Allocating = .init(gpa);
2045 defer aw.deinit();2042 defer aw.deinit();
2046 try errors.renderToWriter(.{}, &aw.writer, .no_color);2043 try errors.renderToWriter(.{}, &aw.writer);
2047 try std.testing.expectEqualStrings(2044 try std.testing.expectEqualStrings(
2048 \\error: unable to unpack2045 \\error: unable to unpack
2049 \\ note: unable to create symlink from 'dir2/file2' to 'filename': SymlinkError2046 \\ note: unable to create symlink from 'dir2/file2' to 'filename': SymlinkError
...@@ -2074,7 +2071,7 @@ test "tarball with duplicate paths" {...@@ -2074,7 +2071,7 @@ test "tarball with duplicate paths" {
2074 defer tmp.cleanup();2071 defer tmp.cleanup();
20752072
2076 const tarball_name = "duplicate_paths.tar.gz";2073 const tarball_name = "duplicate_paths.tar.gz";
2077 try saveEmbedFile(tarball_name, tmp.dir);2074 try saveEmbedFile(io, tarball_name, tmp.dir);
2078 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });2075 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
2079 defer gpa.free(tarball_path);2076 defer gpa.free(tarball_path);
20802077
...@@ -2107,7 +2104,7 @@ test "tarball with excluded duplicate paths" {...@@ -2107,7 +2104,7 @@ test "tarball with excluded duplicate paths" {
2107 defer tmp.cleanup();2104 defer tmp.cleanup();
21082105
2109 const tarball_name = "duplicate_paths_excluded.tar.gz";2106 const tarball_name = "duplicate_paths_excluded.tar.gz";
2110 try saveEmbedFile(tarball_name, tmp.dir);2107 try saveEmbedFile(io, tarball_name, tmp.dir);
2111 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });2108 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
2112 defer gpa.free(tarball_path);2109 defer gpa.free(tarball_path);
21132110
...@@ -2153,7 +2150,7 @@ test "tarball without root folder" {...@@ -2153,7 +2150,7 @@ test "tarball without root folder" {
2153 defer tmp.cleanup();2150 defer tmp.cleanup();
21542151
2155 const tarball_name = "no_root.tar.gz";2152 const tarball_name = "no_root.tar.gz";
2156 try saveEmbedFile(tarball_name, tmp.dir);2153 try saveEmbedFile(io, tarball_name, tmp.dir);
2157 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });2154 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
2158 defer gpa.free(tarball_path);2155 defer gpa.free(tarball_path);
21592156
...@@ -2178,7 +2175,7 @@ test "tarball without root folder" {...@@ -2178,7 +2175,7 @@ test "tarball without root folder" {
2178}2175}
21792176
2180test "set executable bit based on file content" {2177test "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;
2182 const gpa = std.testing.allocator;2179 const gpa = std.testing.allocator;
2183 const io = std.testing.io;2180 const io = std.testing.io;
21842181
...@@ -2186,7 +2183,7 @@ test "set executable bit based on file content" {...@@ -2186,7 +2183,7 @@ test "set executable bit based on file content" {
2186 defer tmp.cleanup();2183 defer tmp.cleanup();
21872184
2188 const tarball_name = "executables.tar.gz";2185 const tarball_name = "executables.tar.gz";
2189 try saveEmbedFile(tarball_name, tmp.dir);2186 try saveEmbedFile(io, tarball_name, tmp.dir);
2190 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });2187 const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name });
2191 defer gpa.free(tarball_path);2188 defer gpa.free(tarball_path);
21922189
...@@ -2210,16 +2207,16 @@ test "set executable bit based on file content" {...@@ -2210,16 +2207,16 @@ test "set executable bit based on file content" {
2210 );2207 );
22112208
2212 var out = try fb.packageDir();2209 var out = try fb.packageDir();
2213 defer out.close();2210 defer out.close(io);
2214 const S = std.posix.S;2211 const S = std.posix.S;
2215 // expect executable bit not set2212 // expect executable bit not set
2216 try std.testing.expect((try out.statFile("file1")).mode & S.IXUSR == 0);2213 try std.testing.expect((try out.statFile(io, "file1", .{})).permissions.toMode() & S.IXUSR == 0);
2217 try std.testing.expect((try out.statFile("script_without_shebang")).mode & S.IXUSR == 0);2214 try std.testing.expect((try out.statFile(io, "script_without_shebang", .{})).permissions.toMode() & S.IXUSR == 0);
2218 // expect executable bit set2215 // expect executable bit set
2219 try std.testing.expect((try out.statFile("hello")).mode & S.IXUSR != 0);2216 try std.testing.expect((try out.statFile(io, "hello", .{})).permissions.toMode() & S.IXUSR != 0);
2220 try std.testing.expect((try out.statFile("script")).mode & S.IXUSR != 0);2217 try std.testing.expect((try out.statFile(io, "script", .{})).permissions.toMode() & S.IXUSR != 0);
2221 try std.testing.expect((try out.statFile("script_with_shebang_without_exec_bit")).mode & S.IXUSR != 0);2218 try std.testing.expect((try out.statFile(io, "script_with_shebang_without_exec_bit", .{})).permissions.toMode() & S.IXUSR != 0);
2222 try std.testing.expect((try out.statFile("hello_ln")).mode & S.IXUSR != 0);2219 try std.testing.expect((try out.statFile(io, "hello_ln", .{})).permissions.toMode() & S.IXUSR != 0);
22232220
2224 //2221 //
2225 // $ ls -al zig-cache/tmp/OCz9ovUcstDjTC_U/zig-global-cache/p/1220fecb4c06a9da8673c87fe8810e15785f1699212f01728eadce094d21effeeef32222 // $ ls -al zig-cache/tmp/OCz9ovUcstDjTC_U/zig-global-cache/p/1220fecb4c06a9da8673c87fe8810e15785f1699212f01728eadce094d21effeeef3
...@@ -2231,12 +2228,12 @@ test "set executable bit based on file content" {...@@ -2231,12 +2228,12 @@ test "set executable bit based on file content" {
2231 // -rwxrwxr-x 1 17 Apr script_with_shebang_without_exec_bit2228 // -rwxrwxr-x 1 17 Apr script_with_shebang_without_exec_bit
2232}2229}
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 {
2235 //const tarball_name = "duplicate_paths_excluded.tar.gz";2232 //const tarball_name = "duplicate_paths_excluded.tar.gz";
2236 const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name);2233 const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name);
2237 var tmp_file = try dir.createFile(tarball_name, .{});2234 var tmp_file = try dir.createFile(io, tarball_name, .{});
2238 defer tmp_file.close();2235 defer tmp_file.close(io);
2239 try tmp_file.writeAll(tarball_content);2236 try tmp_file.writeStreamingAll(io, tarball_content);
2240}2237}
22412238
2242// Builds Fetch with required dependencies, clears dependencies on deinit().2239// Builds Fetch with required dependencies, clears dependencies on deinit().
...@@ -2250,10 +2247,10 @@ const TestFetchBuilder = struct {...@@ -2250,10 +2247,10 @@ const TestFetchBuilder = struct {
2250 self: *TestFetchBuilder,2247 self: *TestFetchBuilder,
2251 allocator: std.mem.Allocator,2248 allocator: std.mem.Allocator,
2252 io: Io,2249 io: Io,
2253 cache_parent_dir: std.fs.Dir,2250 cache_parent_dir: std.Io.Dir,
2254 path_or_url: []const u8,2251 path_or_url: []const u8,
2255 ) !*Fetch {2252 ) !*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
2258 self.http_client = .{ .allocator = allocator, .io = io };2255 self.http_client = .{ .allocator = allocator, .io = io };
2259 self.global_cache_directory = .{ .handle = cache_dir, .path = null };2256 self.global_cache_directory = .{ .handle = cache_dir, .path = null };
...@@ -2301,35 +2298,40 @@ const TestFetchBuilder = struct {...@@ -2301,35 +2298,40 @@ const TestFetchBuilder = struct {
2301 }2298 }
23022299
2303 fn deinit(self: *TestFetchBuilder) void {2300 fn deinit(self: *TestFetchBuilder) void {
2301 const io = self.job_queue.io;
2304 self.fetch.deinit();2302 self.fetch.deinit();
2305 self.job_queue.deinit();2303 self.job_queue.deinit();
2306 self.fetch.prog_node.end();2304 self.fetch.prog_node.end();
2307 self.global_cache_directory.handle.close();2305 self.global_cache_directory.handle.close(io);
2308 self.http_client.deinit();2306 self.http_client.deinit();
2309 }2307 }
23102308
2311 fn packageDir(self: *TestFetchBuilder) !fs.Dir {2309 fn packageDir(self: *TestFetchBuilder) !Io.Dir {
2310 const io = self.job_queue.io;
2312 const root = self.fetch.package_root;2311 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 });
2314 }2313 }
23152314
2316 // Test helper, asserts thet package dir constains expected_files.2315 // Test helper, asserts thet package dir constains expected_files.
2317 // expected_files must be sorted.2316 // expected_files must be sorted.
2318 fn expectPackageFiles(self: *TestFetchBuilder, expected_files: []const []const u8) !void {2317 fn expectPackageFiles(self: *TestFetchBuilder, expected_files: []const []const u8) !void {
2318 const io = self.job_queue.io;
2319 const gpa = std.testing.allocator;
2320
2319 var package_dir = try self.packageDir();2321 var package_dir = try self.packageDir();
2320 defer package_dir.close();2322 defer package_dir.close(io);
23212323
2322 var actual_files: std.ArrayList([]u8) = .empty;2324 var actual_files: std.ArrayList([]u8) = .empty;
2323 defer actual_files.deinit(std.testing.allocator);2325 defer actual_files.deinit(gpa);
2324 defer for (actual_files.items) |file| std.testing.allocator.free(file);2326 defer for (actual_files.items) |file| gpa.free(file);
2325 var walker = try package_dir.walk(std.testing.allocator);2327 var walker = try package_dir.walk(gpa);
2326 defer walker.deinit();2328 defer walker.deinit();
2327 while (try walker.next()) |entry| {2329 while (try walker.next(io)) |entry| {
2328 if (entry.kind != .file) continue;2330 if (entry.kind != .file) continue;
2329 const path = try std.testing.allocator.dupe(u8, entry.path);2331 const path = try gpa.dupe(u8, entry.path);
2330 errdefer std.testing.allocator.free(path);2332 errdefer gpa.free(path);
2331 std.mem.replaceScalar(u8, path, std.fs.path.sep, '/');2333 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);
2333 }2335 }
2334 std.mem.sortUnstable([]u8, actual_files.items, {}, struct {2336 std.mem.sortUnstable([]u8, actual_files.items, {}, struct {
2335 fn lessThan(_: void, a: []u8, b: []u8) bool {2337 fn lessThan(_: void, a: []u8, b: []u8) bool {
...@@ -2346,17 +2348,19 @@ const TestFetchBuilder = struct {...@@ -2346,17 +2348,19 @@ const TestFetchBuilder = struct {
23462348
2347 // Test helper, asserts that fetch has failed with `msg` error message.2349 // Test helper, asserts that fetch has failed with `msg` error message.
2348 fn expectFetchErrors(self: *TestFetchBuilder, notes_len: usize, msg: []const u8) !void {2350 fn expectFetchErrors(self: *TestFetchBuilder, notes_len: usize, msg: []const u8) !void {
2351 const gpa = std.testing.allocator;
2352
2349 var errors = try self.fetch.error_bundle.toOwnedBundle("");2353 var errors = try self.fetch.error_bundle.toOwnedBundle("");
2350 defer errors.deinit(std.testing.allocator);2354 defer errors.deinit(gpa);
23512355
2352 const em = errors.getErrorMessage(errors.getMessages()[0]);2356 const em = errors.getErrorMessage(errors.getMessages()[0]);
2353 try std.testing.expectEqual(1, em.count);2357 try std.testing.expectEqual(1, em.count);
2354 if (notes_len > 0) {2358 if (notes_len > 0) {
2355 try std.testing.expectEqual(notes_len, em.notes_len);2359 try std.testing.expectEqual(notes_len, em.notes_len);
2356 }2360 }
2357 var aw: Io.Writer.Allocating = .init(std.testing.allocator);2361 var aw: Io.Writer.Allocating = .init(gpa);
2358 defer aw.deinit();2362 defer aw.deinit();
2359 try errors.renderToWriter(.{}, &aw.writer, .no_color);2363 try errors.renderToWriter(.{}, &aw.writer);
2360 try std.testing.expectEqualStrings(msg, aw.written());2364 try std.testing.expectEqualStrings(msg, aw.written());
2361 }2365 }
2362};2366};
src/Package/Fetch/git.zig+46-44
...@@ -198,8 +198,8 @@ pub const Repository = struct {...@@ -198,8 +198,8 @@ pub const Repository = struct {
198 repo: *Repository,198 repo: *Repository,
199 allocator: Allocator,199 allocator: Allocator,
200 format: Oid.Format,200 format: Oid.Format,
201 pack_file: *std.fs.File.Reader,201 pack_file: *Io.File.Reader,
202 index_file: *std.fs.File.Reader,202 index_file: *Io.File.Reader,
203 ) !void {203 ) !void {
204 repo.* = .{ .odb = undefined };204 repo.* = .{ .odb = undefined };
205 try repo.odb.init(allocator, format, pack_file, index_file);205 try repo.odb.init(allocator, format, pack_file, index_file);
...@@ -213,7 +213,8 @@ pub const Repository = struct {...@@ -213,7 +213,8 @@ pub const Repository = struct {
213 /// Checks out the repository at `commit_oid` to `worktree`.213 /// Checks out the repository at `commit_oid` to `worktree`.
214 pub fn checkout(214 pub fn checkout(
215 repository: *Repository,215 repository: *Repository,
216 worktree: std.fs.Dir,216 io: Io,
217 worktree: Io.Dir,
217 commit_oid: Oid,218 commit_oid: Oid,
218 diagnostics: *Diagnostics,219 diagnostics: *Diagnostics,
219 ) !void {220 ) !void {
...@@ -223,13 +224,14 @@ pub const Repository = struct {...@@ -223,13 +224,14 @@ pub const Repository = struct {
223 if (commit_object.type != .commit) return error.NotACommit;224 if (commit_object.type != .commit) return error.NotACommit;
224 break :tree_oid try getCommitTree(repository.odb.format, commit_object.data);225 break :tree_oid try getCommitTree(repository.odb.format, commit_object.data);
225 };226 };
226 try repository.checkoutTree(worktree, tree_oid, "", diagnostics);227 try repository.checkoutTree(io, worktree, tree_oid, "", diagnostics);
227 }228 }
228229
229 /// Checks out the tree at `tree_oid` to `worktree`.230 /// Checks out the tree at `tree_oid` to `worktree`.
230 fn checkoutTree(231 fn checkoutTree(
231 repository: *Repository,232 repository: *Repository,
232 dir: std.fs.Dir,233 io: Io,
234 dir: Io.Dir,
233 tree_oid: Oid,235 tree_oid: Oid,
234 current_path: []const u8,236 current_path: []const u8,
235 diagnostics: *Diagnostics,237 diagnostics: *Diagnostics,
...@@ -251,18 +253,18 @@ pub const Repository = struct {...@@ -251,18 +253,18 @@ pub const Repository = struct {
251 while (try tree_iter.next()) |entry| {253 while (try tree_iter.next()) |entry| {
252 switch (entry.type) {254 switch (entry.type) {
253 .directory => {255 .directory => {
254 try dir.makeDir(entry.name);256 try dir.createDir(io, entry.name, .default_dir);
255 var subdir = try dir.openDir(entry.name, .{});257 var subdir = try dir.openDir(io, entry.name, .{});
256 defer subdir.close();258 defer subdir.close(io);
257 const sub_path = try std.fs.path.join(repository.odb.allocator, &.{ current_path, entry.name });259 const sub_path = try std.fs.path.join(repository.odb.allocator, &.{ current_path, entry.name });
258 defer repository.odb.allocator.free(sub_path);260 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);
260 },262 },
261 .file => {263 .file => {
262 try repository.odb.seekOid(entry.oid);264 try repository.odb.seekOid(entry.oid);
263 const file_object = try repository.odb.readObject();265 const file_object = try repository.odb.readObject();
264 if (file_object.type != .blob) return error.InvalidFile;266 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| {
266 const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name });268 const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name });
267 errdefer diagnostics.allocator.free(file_name);269 errdefer diagnostics.allocator.free(file_name);
268 try diagnostics.errors.append(diagnostics.allocator, .{ .unable_to_create_file = .{270 try diagnostics.errors.append(diagnostics.allocator, .{ .unable_to_create_file = .{
...@@ -271,15 +273,15 @@ pub const Repository = struct {...@@ -271,15 +273,15 @@ pub const Repository = struct {
271 } });273 } });
272 continue;274 continue;
273 };275 };
274 defer file.close();276 defer file.close(io);
275 try file.writeAll(file_object.data);277 try file.writePositionalAll(io, file_object.data, 0);
276 },278 },
277 .symlink => {279 .symlink => {
278 try repository.odb.seekOid(entry.oid);280 try repository.odb.seekOid(entry.oid);
279 const symlink_object = try repository.odb.readObject();281 const symlink_object = try repository.odb.readObject();
280 if (symlink_object.type != .blob) return error.InvalidFile;282 if (symlink_object.type != .blob) return error.InvalidFile;
281 const link_name = symlink_object.data;283 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| {
283 const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name });285 const file_name = try std.fs.path.join(diagnostics.allocator, &.{ current_path, entry.name });
284 errdefer diagnostics.allocator.free(file_name);286 errdefer diagnostics.allocator.free(file_name);
285 const link_name_dup = try diagnostics.allocator.dupe(u8, link_name);287 const link_name_dup = try diagnostics.allocator.dupe(u8, link_name);
...@@ -294,7 +296,7 @@ pub const Repository = struct {...@@ -294,7 +296,7 @@ pub const Repository = struct {
294 .gitlink => {296 .gitlink => {
295 // Consistent with git archive behavior, create the directory but297 // Consistent with git archive behavior, create the directory but
296 // do nothing else298 // do nothing else
297 try dir.makeDir(entry.name);299 try dir.createDir(io, entry.name, .default_dir);
298 },300 },
299 }301 }
300 }302 }
...@@ -370,9 +372,9 @@ pub const Repository = struct {...@@ -370,9 +372,9 @@ pub const Repository = struct {
370/// [pack-format](https://git-scm.com/docs/pack-format).372/// [pack-format](https://git-scm.com/docs/pack-format).
371const Odb = struct {373const Odb = struct {
372 format: Oid.Format,374 format: Oid.Format,
373 pack_file: *std.fs.File.Reader,375 pack_file: *Io.File.Reader,
374 index_header: IndexHeader,376 index_header: IndexHeader,
375 index_file: *std.fs.File.Reader,377 index_file: *Io.File.Reader,
376 cache: ObjectCache = .{},378 cache: ObjectCache = .{},
377 allocator: Allocator,379 allocator: Allocator,
378380
...@@ -381,8 +383,8 @@ const Odb = struct {...@@ -381,8 +383,8 @@ const Odb = struct {
381 odb: *Odb,383 odb: *Odb,
382 allocator: Allocator,384 allocator: Allocator,
383 format: Oid.Format,385 format: Oid.Format,
384 pack_file: *std.fs.File.Reader,386 pack_file: *Io.File.Reader,
385 index_file: *std.fs.File.Reader,387 index_file: *Io.File.Reader,
386 ) !void {388 ) !void {
387 try pack_file.seekTo(0);389 try pack_file.seekTo(0);
388 try index_file.seekTo(0);390 try index_file.seekTo(0);
...@@ -1270,8 +1272,8 @@ const IndexEntry = struct {...@@ -1270,8 +1272,8 @@ const IndexEntry = struct {
1270pub fn indexPack(1272pub fn indexPack(
1271 allocator: Allocator,1273 allocator: Allocator,
1272 format: Oid.Format,1274 format: Oid.Format,
1273 pack: *std.fs.File.Reader,1275 pack: *Io.File.Reader,
1274 index_writer: *std.fs.File.Writer,1276 index_writer: *Io.File.Writer,
1275) !void {1277) !void {
1276 try pack.seekTo(0);1278 try pack.seekTo(0);
12771279
...@@ -1370,7 +1372,7 @@ pub fn indexPack(...@@ -1370,7 +1372,7 @@ pub fn indexPack(
1370fn indexPackFirstPass(1372fn indexPackFirstPass(
1371 allocator: Allocator,1373 allocator: Allocator,
1372 format: Oid.Format,1374 format: Oid.Format,
1373 pack: *std.fs.File.Reader,1375 pack: *Io.File.Reader,
1374 index_entries: *std.AutoHashMapUnmanaged(Oid, IndexEntry),1376 index_entries: *std.AutoHashMapUnmanaged(Oid, IndexEntry),
1375 pending_deltas: *std.ArrayList(IndexEntry),1377 pending_deltas: *std.ArrayList(IndexEntry),
1376) !Oid {1378) !Oid {
...@@ -1423,7 +1425,7 @@ fn indexPackFirstPass(...@@ -1423,7 +1425,7 @@ fn indexPackFirstPass(
1423fn indexPackHashDelta(1425fn indexPackHashDelta(
1424 allocator: Allocator,1426 allocator: Allocator,
1425 format: Oid.Format,1427 format: Oid.Format,
1426 pack: *std.fs.File.Reader,1428 pack: *Io.File.Reader,
1427 delta: IndexEntry,1429 delta: IndexEntry,
1428 index_entries: std.AutoHashMapUnmanaged(Oid, IndexEntry),1430 index_entries: std.AutoHashMapUnmanaged(Oid, IndexEntry),
1429 cache: *ObjectCache,1431 cache: *ObjectCache,
...@@ -1475,7 +1477,7 @@ fn indexPackHashDelta(...@@ -1475,7 +1477,7 @@ fn indexPackHashDelta(
1475fn resolveDeltaChain(1477fn resolveDeltaChain(
1476 allocator: Allocator,1478 allocator: Allocator,
1477 format: Oid.Format,1479 format: Oid.Format,
1478 pack: *std.fs.File.Reader,1480 pack: *Io.File.Reader,
1479 base_object: Object,1481 base_object: Object,
1480 delta_offsets: []const u64,1482 delta_offsets: []const u64,
1481 cache: *ObjectCache,1483 cache: *ObjectCache,
...@@ -1582,17 +1584,17 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u...@@ -1582,17 +1584,17 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u
15821584
1583 var git_dir = testing.tmpDir(.{});1585 var git_dir = testing.tmpDir(.{});
1584 defer git_dir.cleanup();1586 defer git_dir.cleanup();
1585 var pack_file = try git_dir.dir.createFile("testrepo.pack", .{ .read = true });1587 var pack_file = try git_dir.dir.createFile(io, "testrepo.pack", .{ .read = true });
1586 defer pack_file.close();1588 defer pack_file.close(io);
1587 try pack_file.writeAll(testrepo_pack);1589 try pack_file.writeStreamingAll(io, testrepo_pack);
15881590
1589 var pack_file_buffer: [2000]u8 = undefined;1591 var pack_file_buffer: [2000]u8 = undefined;
1590 var pack_file_reader = pack_file.reader(io, &pack_file_buffer);1592 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 });1594 var index_file = try git_dir.dir.createFile(io, "testrepo.idx", .{ .read = true });
1593 defer index_file.close();1595 defer index_file.close(io);
1594 var index_file_buffer: [2000]u8 = undefined;1596 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);
1596 try indexPack(testing.allocator, format, &pack_file_reader, &index_file_writer);1598 try indexPack(testing.allocator, format, &pack_file_reader, &index_file_writer);
15971599
1598 // Arbitrary size limit on files read while checking the repository contents1600 // 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...@@ -1600,7 +1602,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u
1600 const max_file_size = 8192;1602 const max_file_size = 8192;
16011603
1602 if (!skip_checksums) {1604 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));
1604 defer testing.allocator.free(index_file_data);1606 defer testing.allocator.free(index_file_data);
1605 // testrepo.idx is generated by Git. The index created by this file should1607 // testrepo.idx is generated by Git. The index created by this file should
1606 // match it exactly. Running `git verify-pack -v testrepo.pack` can verify1608 // 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...@@ -1621,7 +1623,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u
16211623
1622 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };1624 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
1623 defer diagnostics.deinit();1625 defer diagnostics.deinit();
1624 try repository.checkout(worktree.dir, commit_id, &diagnostics);1626 try repository.checkout(io, worktree.dir, commit_id, &diagnostics);
1625 try testing.expect(diagnostics.errors.items.len == 0);1627 try testing.expect(diagnostics.errors.items.len == 0);
16261628
1627 const expected_files: []const []const u8 = &.{1629 const expected_files: []const []const u8 = &.{
...@@ -1646,7 +1648,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u...@@ -1646,7 +1648,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u
1646 defer for (actual_files.items) |file| testing.allocator.free(file);1648 defer for (actual_files.items) |file| testing.allocator.free(file);
1647 var walker = try worktree.dir.walk(testing.allocator);1649 var walker = try worktree.dir.walk(testing.allocator);
1648 defer walker.deinit();1650 defer walker.deinit();
1649 while (try walker.next()) |entry| {1651 while (try walker.next(io)) |entry| {
1650 if (entry.kind != .file) continue;1652 if (entry.kind != .file) continue;
1651 const path = try testing.allocator.dupe(u8, entry.path);1653 const path = try testing.allocator.dupe(u8, entry.path);
1652 errdefer testing.allocator.free(path);1654 errdefer testing.allocator.free(path);
...@@ -1676,7 +1678,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u...@@ -1676,7 +1678,7 @@ fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u
1676 \\revision 191678 \\revision 19
1677 \\1679 \\
1678 ;1680 ;
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));
1680 defer testing.allocator.free(actual_file_contents);1682 defer testing.allocator.free(actual_file_contents);
1681 try testing.expectEqualStrings(expected_file_contents, actual_file_contents);1683 try testing.expectEqualStrings(expected_file_contents, actual_file_contents);
1682}1684}
...@@ -1700,7 +1702,7 @@ test "SHA-256 packfile indexing and checkout" {...@@ -1700,7 +1702,7 @@ test "SHA-256 packfile indexing and checkout" {
1700pub fn main() !void {1702pub fn main() !void {
1701 const allocator = std.heap.smp_allocator;1703 const allocator = std.heap.smp_allocator;
17021704
1703 var threaded: Io.Threaded = .init(allocator);1705 var threaded: Io.Threaded = .init(allocator, .{});
1704 defer threaded.deinit();1706 defer threaded.deinit();
1705 const io = threaded.io();1707 const io = threaded.io();
17061708
...@@ -1712,23 +1714,23 @@ pub fn main() !void {...@@ -1712,23 +1714,23 @@ pub fn main() !void {
17121714
1713 const format = std.meta.stringToEnum(Oid.Format, args[1]) orelse return error.InvalidFormat;1715 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], .{});1717 var pack_file = try Io.Dir.cwd().openFile(io, args[2], .{});
1716 defer pack_file.close();1718 defer pack_file.close(io);
1717 var pack_file_buffer: [4096]u8 = undefined;1719 var pack_file_buffer: [4096]u8 = undefined;
1718 var pack_file_reader = pack_file.reader(io, &pack_file_buffer);1720 var pack_file_reader = pack_file.reader(io, &pack_file_buffer);
17191721
1720 const commit = try Oid.parse(format, args[3]);1722 const commit = try Oid.parse(format, args[3]);
1721 var worktree = try std.fs.cwd().makeOpenPath(args[4], .{});1723 var worktree = try Io.Dir.cwd().createDirPathOpen(io, args[4], .{});
1722 defer worktree.close();1724 defer worktree.close(io);
17231725
1724 var git_dir = try worktree.makeOpenPath(".git", .{});1726 var git_dir = try worktree.createDirPathOpen(io, ".git", .{});
1725 defer git_dir.close();1727 defer git_dir.close(io);
17261728
1727 std.debug.print("Starting index...\n", .{});1729 std.debug.print("Starting index...\n", .{});
1728 var index_file = try git_dir.createFile("idx", .{ .read = true });1730 var index_file = try git_dir.createFile(io, "idx", .{ .read = true });
1729 defer index_file.close();1731 defer index_file.close(io);
1730 var index_file_buffer: [4096]u8 = undefined;1732 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);
1732 try indexPack(allocator, format, &pack_file_reader, &index_file_writer);1734 try indexPack(allocator, format, &pack_file_reader, &index_file_writer);
17331735
1734 std.debug.print("Starting checkout...\n", .{});1736 std.debug.print("Starting checkout...\n", .{});
...@@ -1738,7 +1740,7 @@ pub fn main() !void {...@@ -1738,7 +1740,7 @@ pub fn main() !void {
1738 defer repository.deinit();1740 defer repository.deinit();
1739 var diagnostics: Diagnostics = .{ .allocator = allocator };1741 var diagnostics: Diagnostics = .{ .allocator = allocator };
1740 defer diagnostics.deinit();1742 defer diagnostics.deinit();
1741 try repository.checkout(worktree, commit, &diagnostics);1743 try repository.checkout(io, worktree, commit, &diagnostics);
17421744
1743 for (diagnostics.errors.items) |err| {1745 for (diagnostics.errors.items) |err| {
1744 std.debug.print("Diagnostic: {}\n", .{err});1746 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...@@ -2668,16 +2668,18 @@ fn failWithTypeMismatch(sema: *Sema, block: *Block, src: LazySrcLoc, expected: T
26682668
2669pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg) error{ AnalysisFail, OutOfMemory } {2669pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg) error{ AnalysisFail, OutOfMemory } {
2670 @branchHint(.cold);2670 @branchHint(.cold);
2671 const gpa = sema.gpa;
2672 const zcu = sema.pt.zcu;2671 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) {
2675 var wip_errors: std.zig.ErrorBundle.Wip = undefined;2677 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
2676 wip_errors.init(gpa) catch @panic("out of memory");2678 wip_errors.init(gpa) catch @panic("out of memory");
2677 Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory");2679 Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory");
2678 std.debug.print("compile error during Sema:\n", .{});2680 std.debug.print("compile error during Sema:\n", .{});
2679 var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");2681 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");
2681 std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{});2683 std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{});
2682 }2684 }
26832685
src/Zcu.zig+20-17
...@@ -1076,11 +1076,11 @@ pub const File = struct {...@@ -1076,11 +1076,11 @@ pub const File = struct {
10761076
1077 var f = f: {1077 var f = f: {
1078 const dir, const sub_path = file.path.openInfo(zcu.comp.dirs);1078 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, .{});
1080 };1080 };
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) {
1084 error.Streaming => {1084 error.Streaming => {
1085 // Since `file.stat` is populated, this was previously a file stream; since it is1085 // Since `file.stat` is populated, this was previously a file stream; since it is
1086 // now not a file stream, it must have changed.1086 // now not a file stream, it must have changed.
...@@ -1200,7 +1200,7 @@ pub const EmbedFile = struct {...@@ -1200,7 +1200,7 @@ pub const EmbedFile = struct {
1200 /// `.none` means the file was not loaded, so `stat` is undefined.1200 /// `.none` means the file was not loaded, so `stat` is undefined.
1201 val: InternPool.Index,1201 val: InternPool.Index,
1202 /// If this is `null` and `val` is `.none`, the file has never been loaded.1202 /// 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}),
1204 stat: Cache.File.Stat,1204 stat: Cache.File.Stat,
12051205
1206 pub const Index = enum(u32) {1206 pub const Index = enum(u32) {
...@@ -2813,8 +2813,8 @@ pub fn init(zcu: *Zcu, gpa: Allocator, io: Io, thread_count: usize) !void {...@@ -2813,8 +2813,8 @@ pub fn init(zcu: *Zcu, gpa: Allocator, io: Io, thread_count: usize) !void {
28132813
2814pub fn deinit(zcu: *Zcu) void {2814pub fn deinit(zcu: *Zcu) void {
2815 const comp = zcu.comp;2815 const comp = zcu.comp;
2816 const gpa = comp.gpa;
2817 const io = comp.io;2816 const io = comp.io;
2817 const gpa = zcu.gpa;
2818 {2818 {
2819 const pt: Zcu.PerThread = .activate(zcu, .main);2819 const pt: Zcu.PerThread = .activate(zcu, .main);
2820 defer pt.deactivate();2820 defer pt.deactivate();
...@@ -2835,8 +2835,8 @@ pub fn deinit(zcu: *Zcu) void {...@@ -2835,8 +2835,8 @@ pub fn deinit(zcu: *Zcu) void {
2835 }2835 }
2836 zcu.embed_table.deinit(gpa);2836 zcu.embed_table.deinit(gpa);
28372837
2838 zcu.local_zir_cache.handle.close();2838 zcu.local_zir_cache.handle.close(io);
2839 zcu.global_zir_cache.handle.close();2839 zcu.global_zir_cache.handle.close(io);
28402840
2841 for (zcu.failed_analysis.values()) |value| value.destroy(gpa);2841 for (zcu.failed_analysis.values()) |value| value.destroy(gpa);
2842 for (zcu.failed_codegen.values()) |value| value.destroy(gpa);2842 for (zcu.failed_codegen.values()) |value| value.destroy(gpa);
...@@ -2900,7 +2900,7 @@ pub fn deinit(zcu: *Zcu) void {...@@ -2900,7 +2900,7 @@ pub fn deinit(zcu: *Zcu) void {
29002900
2901 if (zcu.resolved_references) |*r| r.deinit(gpa);2901 if (zcu.resolved_references) |*r| r.deinit(gpa);
29022902
2903 if (zcu.comp.debugIncremental()) {2903 if (comp.debugIncremental()) {
2904 zcu.incremental_debug_state.deinit(gpa);2904 zcu.incremental_debug_state.deinit(gpa);
2905 }2905 }
2906 }2906 }
...@@ -2927,7 +2927,7 @@ comptime {...@@ -2927,7 +2927,7 @@ comptime {
2927 }2927 }
2928}2928}
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 {
2931 var buffer: [2000]u8 = undefined;2931 var buffer: [2000]u8 = undefined;
2932 var file_reader = cache_file.reader(io, &buffer);2932 var file_reader = cache_file.reader(io, &buffer);
2933 return result: {2933 return result: {
...@@ -2986,7 +2986,12 @@ pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_br: *Io.Reader...@@ -2986,7 +2986,12 @@ pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_br: *Io.Reader
2986 return zir;2986 return zir;
2987}2987}
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 {
2990 const safety_buffer = if (data_has_safety_tag)2995 const safety_buffer = if (data_has_safety_tag)
2991 try gpa.alloc([8]u8, zir.instructions.len)2996 try gpa.alloc([8]u8, zir.instructions.len)
2992 else2997 else
...@@ -3020,13 +3025,12 @@ pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.S...@@ -3020,13 +3025,12 @@ pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.S
3020 zir.string_bytes,3025 zir.string_bytes,
3021 @ptrCast(zir.extra),3026 @ptrCast(zir.extra),
3022 };3027 };
3023 var cache_fw = cache_file.writer(&.{});3028 cache_file_writer.interface.writeVecAll(&vecs) catch |err| switch (err) {
3024 cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) {3029 error.WriteFailed => return cache_file_writer.err.?,
3025 error.WriteFailed => return cache_fw.err.?,
3026 };3030 };
3027}3031}
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 {
3030 const header: Zoir.Header = .{3034 const header: Zoir.Header = .{
3031 .nodes_len = @intCast(zoir.nodes.len),3035 .nodes_len = @intCast(zoir.nodes.len),
3032 .extra_len = @intCast(zoir.extra.len),3036 .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...@@ -3050,9 +3054,8 @@ pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir
3050 @ptrCast(zoir.compile_errors),3054 @ptrCast(zoir.compile_errors),
3051 @ptrCast(zoir.error_notes),3055 @ptrCast(zoir.error_notes),
3052 };3056 };
3053 var cache_fw = cache_file.writer(&.{});3057 cache_file_writer.interface.writeVecAll(&vecs) catch |err| switch (err) {
3054 cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) {3058 error.WriteFailed => return cache_file_writer.err.?,
3055 error.WriteFailed => return cache_fw.err.?,
3056 };3059 };
3057}3060}
30583061
src/Zcu/PerThread.zig+54-33
...@@ -94,11 +94,11 @@ pub fn updateFile(...@@ -94,11 +94,11 @@ pub fn updateFile(
94 // In any case we need to examine the stat of the file to determine the course of action.94 // In any case we need to examine the stat of the file to determine the course of action.
95 var source_file = f: {95 var source_file = f: {
96 const dir, const sub_path = file.path.openInfo(comp.dirs);96 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, .{});
98 };98 };
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
103 const want_local_cache = switch (file.path.root) {103 const want_local_cache = switch (file.path.root) {
104 .none, .local_cache => true,104 .none, .local_cache => true,
...@@ -118,7 +118,7 @@ pub fn updateFile(...@@ -118,7 +118,7 @@ pub fn updateFile(
118 const zir_dir = cache_directory.handle;118 const zir_dir = cache_directory.handle;
119119
120 // Determine whether we need to reload the file from disk and redo parsing and AstGen.120 // 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) {
122 .never_loaded, .retryable_failure => lock: {122 .never_loaded, .retryable_failure => lock: {
123 // First, load the cached ZIR code, if any.123 // First, load the cached ZIR code, if any.
124 log.debug("AstGen checking cache: {f} (local={}, digest={s})", .{124 log.debug("AstGen checking cache: {f} (local={}, digest={s})", .{
...@@ -170,7 +170,7 @@ pub fn updateFile(...@@ -170,7 +170,7 @@ pub fn updateFile(
170 // version. Likewise if we're working on AstGen and another process asks for170 // version. Likewise if we're working on AstGen and another process asks for
171 // the cached file, they'll get it.171 // the cached file, they'll get it.
172 const cache_file = while (true) {172 const cache_file = while (true) {
173 break zir_dir.createFile(&hex_digest, .{173 break zir_dir.createFile(io, &hex_digest, .{
174 .read = true,174 .read = true,
175 .truncate = false,175 .truncate = false,
176 .lock = lock,176 .lock = lock,
...@@ -196,7 +196,7 @@ pub fn updateFile(...@@ -196,7 +196,7 @@ pub fn updateFile(
196 cache_directory,196 cache_directory,
197 });197 });
198 }198 }
199 break zir_dir.createFile(&hex_digest, .{199 break zir_dir.createFile(io, &hex_digest, .{
200 .read = true,200 .read = true,
201 .truncate = false,201 .truncate = false,
202 .lock = lock,202 .lock = lock,
...@@ -215,7 +215,7 @@ pub fn updateFile(...@@ -215,7 +215,7 @@ pub fn updateFile(
215 else => |e| return e, // Retryable errors are handled at callsite.215 else => |e| return e, // Retryable errors are handled at callsite.
216 };216 };
217 };217 };
218 defer cache_file.close();218 defer cache_file.close(io);
219219
220 // Under `--time-report`, ignore cache hits; do the work anyway for those juicy numbers.220 // Under `--time-report`, ignore cache hits; do the work anyway for those juicy numbers.
221 const ignore_hit = comp.time_report != null;221 const ignore_hit = comp.time_report != null;
...@@ -238,18 +238,13 @@ pub fn updateFile(...@@ -238,18 +238,13 @@ pub fn updateFile(
238 if (builtin.os.tag == .wasi or lock == .exclusive) break true;238 if (builtin.os.tag == .wasi or lock == .exclusive) break true;
239 // Otherwise, unlock to give someone a chance to get the exclusive lock239 // Otherwise, unlock to give someone a chance to get the exclusive lock
240 // and then upgrade to an exclusive lock.240 // and then upgrade to an exclusive lock.
241 cache_file.unlock();241 cache_file.unlock(io);
242 lock = .exclusive;242 lock = .exclusive;
243 try cache_file.lock(lock);243 try cache_file.lock(io, lock);
244 };244 };
245245
246 if (need_update) {246 if (need_update) {
247 // The cache is definitely stale so delete the contents to avoid an underwrite later.247 var cache_file_writer: Io.File.Writer = .init(cache_file, io, &.{});
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);
253248
254 if (stat.size > std.math.maxInt(u32))249 if (stat.size > std.math.maxInt(u32))
255 return error.FileTooBig;250 return error.FileTooBig;
...@@ -278,22 +273,28 @@ pub fn updateFile(...@@ -278,22 +273,28 @@ pub fn updateFile(
278 switch (file.getMode()) {273 switch (file.getMode()) {
279 .zig => {274 .zig => {
280 file.zir = try AstGen.generate(gpa, file.tree.?);275 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) {
282 error.OutOfMemory => |e| return e,277 error.OutOfMemory => |e| return e,
283 else => log.warn("unable to write cached ZIR code for {f} to {f}{s}: {s}", .{278 else => log.warn("unable to write cached ZIR code for {f} to {f}{s}: {t}", .{
284 file.path.fmt(comp), cache_directory, &hex_digest, @errorName(err),279 file.path.fmt(comp), cache_directory, &hex_digest, err,
285 }),280 }),
286 };281 };
287 },282 },
288 .zon => {283 .zon => {
289 file.zoir = try ZonGen.generate(gpa, file.tree.?, .{});284 file.zoir = try ZonGen.generate(gpa, file.tree.?, .{});
290 Zcu.saveZoirCache(cache_file, stat, file.zoir.?) catch |err| {285 Zcu.saveZoirCache(&cache_file_writer, stat, file.zoir.?) catch |err| {
291 log.warn("unable to write cached ZOIR code for {f} to {f}{s}: {s}", .{286 log.warn("unable to write cached ZOIR code for {f} to {f}{s}: {t}", .{
292 file.path.fmt(comp), cache_directory, &hex_digest, @errorName(err),287 file.path.fmt(comp), cache_directory, &hex_digest, err,
293 });288 });
294 };289 };
295 },290 },
296 }291 }
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
297 if (timer.finish()) |ns_astgen| {298 if (timer.finish()) |ns_astgen| {
298 comp.mutex.lockUncancelable(io);299 comp.mutex.lockUncancelable(io);
299 defer comp.mutex.unlock(io);300 defer comp.mutex.unlock(io);
...@@ -346,8 +347,8 @@ pub fn updateFile(...@@ -346,8 +347,8 @@ pub fn updateFile(
346347
347fn loadZirZoirCache(348fn loadZirZoirCache(
348 zcu: *Zcu,349 zcu: *Zcu,
349 cache_file: std.fs.File,350 cache_file: Io.File,
350 stat: std.fs.File.Stat,351 stat: Io.File.Stat,
351 file: *Zcu.File,352 file: *Zcu.File,
352 comptime mode: Ast.Mode,353 comptime mode: Ast.Mode,
353) !enum { success, invalid, truncated, stale } {354) !enum { success, invalid, truncated, stale } {
...@@ -2466,11 +2467,11 @@ fn updateEmbedFileInner(...@@ -2466,11 +2467,11 @@ fn updateEmbedFileInner(
24662467
2467 var file = f: {2468 var file = f: {
2468 const dir, const sub_path = ef.path.openInfo(zcu.comp.dirs);2469 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, .{});
2470 };2471 };
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
2475 if (ef.val != .none) {2476 if (ef.val != .none) {
2476 const old_stat = ef.stat;2477 const old_stat = ef.stat;
...@@ -4524,12 +4525,14 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru...@@ -4524,12 +4525,14 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru
4524 .stage2_llvm,4525 .stage2_llvm,
4525 => {},4526 => {},
4526 },4527 },
4528 error.Canceled => |e| return e,
4527 }4529 }
4528 return error.AlreadyReported;4530 return error.AlreadyReported;
4529 };4531 };
4530}4532}
4531fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) error{4533fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) error{
4532 OutOfMemory,4534 OutOfMemory,
4535 Canceled,
4533 CodegenFail,4536 CodegenFail,
4534 NoLinkFile,4537 NoLinkFile,
4535 BackendDoesNotProduceMir,4538 BackendDoesNotProduceMir,
...@@ -4555,12 +4558,16 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e...@@ -4555,12 +4558,16 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e
4555 null;4558 null;
4556 defer if (liveness) |*l| l.deinit(gpa);4559 defer if (liveness) |*l| l.deinit(gpa);
45574560
4558 if (build_options.enable_debug_extensions and comp.verbose_air) {4561 if (build_options.enable_debug_extensions and comp.verbose_air) p: {
4559 const stderr, _ = std.debug.lockStderrWriter(&.{});4562 const io = comp.io;
4560 defer std.debug.unlockStderrWriter();4563 const stderr = try io.lockStderr(&.{}, null);
4561 stderr.print("# Begin Function AIR: {f}:\n", .{fqn.fmt(ip)}) catch {};4564 defer io.unlockStderr();
4562 air.write(stderr, pt, liveness);4565 printVerboseAir(pt, liveness, fqn, air, &stderr.file_writer.interface) catch |err| switch (err) {
4563 stderr.print("# End Function AIR: {f}\n\n", .{fqn.fmt(ip)}) catch {};4566 error.WriteFailed => switch (stderr.file_writer.err.?) {
4567 error.Canceled => |e| return e,
4568 else => break :p,
4569 },
4570 };
4564 }4571 }
45654572
4566 if (std.debug.runtime_safety) verify_liveness: {4573 if (std.debug.runtime_safety) verify_liveness: {
...@@ -4575,7 +4582,7 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e...@@ -4575,7 +4582,7 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e
45754582
4576 verify.verify() catch |err| switch (err) {4583 verify.verify() catch |err| switch (err) {
4577 error.OutOfMemory => return error.OutOfMemory,4584 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}),
4579 };4586 };
4580 }4587 }
45814588
...@@ -4611,3 +4618,17 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e...@@ -4611,3 +4618,17 @@ fn runCodegenInner(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) e
4611 => return zcu.codegenFail(nav, "unable to codegen: {s}", .{@errorName(err)}),4618 => return zcu.codegenFail(nav, "unable to codegen: {s}", .{@errorName(err)}),
4612 };4619 };
4613}4620}
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(...@@ -11273,15 +11273,17 @@ fn initValueAdvanced(
11273 return @enumFromInt(isel.values.items.len);11273 return @enumFromInt(isel.values.items.len);
11274}11274}
11275pub fn dumpValues(isel: *Select, which: enum { only_referenced, all }) void {11275pub 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
11280 const zcu = isel.pt.zcu;11276 const zcu = isel.pt.zcu;
11281 const gpa = zcu.gpa;11277 const gpa = zcu.gpa;
11282 const ip = &zcu.intern_pool;11278 const ip = &zcu.intern_pool;
11283 const nav = ip.getNav(isel.nav_index);11279 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
11285 var reverse_live_values: std.AutoArrayHashMapUnmanaged(Value.Index, std.ArrayList(Air.Inst.Index)) = .empty;11287 var reverse_live_values: std.AutoArrayHashMapUnmanaged(Value.Index, std.ArrayList(Air.Inst.Index)) = .empty;
11286 defer {11288 defer {
11287 for (reverse_live_values.values()) |*list| list.deinit(gpa);11289 for (reverse_live_values.values()) |*list| list.deinit(gpa);
src/codegen/llvm.zig+21-17
...@@ -1,19 +1,22 @@...@@ -1,19 +1,22 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
3const assert = std.debug.assert;5const assert = std.debug.assert;
4const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
5const log = std.log.scoped(.codegen);7const log = std.log.scoped(.codegen);
6const math = std.math;8const math = std.math;
7const DW = std.dwarf;9const DW = std.dwarf;
8
9const Builder = std.zig.llvm.Builder;10const Builder = std.zig.llvm.Builder;
11
12const build_options = @import("build_options");
10const llvm = if (build_options.have_llvm)13const llvm = if (build_options.have_llvm)
11 @import("llvm/bindings.zig")14 @import("llvm/bindings.zig")
12else15else
13 @compileError("LLVM unavailable");16 @compileError("LLVM unavailable");
17
14const link = @import("../link.zig");18const link = @import("../link.zig");
15const Compilation = @import("../Compilation.zig");19const Compilation = @import("../Compilation.zig");
16const build_options = @import("build_options");
17const Zcu = @import("../Zcu.zig");20const Zcu = @import("../Zcu.zig");
18const InternPool = @import("../InternPool.zig");21const InternPool = @import("../InternPool.zig");
19const Package = @import("../Package.zig");22const Package = @import("../Package.zig");
...@@ -799,6 +802,7 @@ pub const Object = struct {...@@ -799,6 +802,7 @@ pub const Object = struct {
799 pub fn emit(o: *Object, pt: Zcu.PerThread, options: EmitOptions) error{ LinkFailure, OutOfMemory }!void {802 pub fn emit(o: *Object, pt: Zcu.PerThread, options: EmitOptions) error{ LinkFailure, OutOfMemory }!void {
800 const zcu = pt.zcu;803 const zcu = pt.zcu;
801 const comp = zcu.comp;804 const comp = zcu.comp;
805 const io = comp.io;
802 const diags = &comp.link_diags;806 const diags = &comp.link_diags;
803807
804 {808 {
...@@ -961,10 +965,10 @@ pub const Object = struct {...@@ -961,10 +965,10 @@ pub const Object = struct {
961 const context, const module = emit: {965 const context, const module = emit: {
962 if (options.pre_ir_path) |path| {966 if (options.pre_ir_path) |path| {
963 if (std.mem.eql(u8, path, "-")) {967 if (std.mem.eql(u8, path, "-")) {
964 o.builder.dump();968 o.builder.dump(io);
965 } else {969 } else {
966 o.builder.printToFilePath(std.fs.cwd(), path) catch |err| {970 o.builder.printToFilePath(io, Io.Dir.cwd(), path) catch |err| {
967 log.err("failed printing LLVM module to \"{s}\": {s}", .{ path, @errorName(err) });971 log.err("failed printing LLVM module to \"{s}\": {t}", .{ path, err });
968 };972 };
969 }973 }
970 }974 }
...@@ -977,26 +981,26 @@ pub const Object = struct {...@@ -977,26 +981,26 @@ pub const Object = struct {
977 o.builder.clearAndFree();981 o.builder.clearAndFree();
978982
979 if (options.pre_bc_path) |path| {983 if (options.pre_bc_path) |path| {
980 var file = std.fs.cwd().createFile(path, .{}) catch |err|984 var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err|
981 return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) });985 return diags.fail("failed to create '{s}': {t}", .{ path, err });
982 defer file.close();986 defer file.close(io);
983987
984 const ptr: [*]const u8 = @ptrCast(bitcode.ptr);988 const ptr: [*]const u8 = @ptrCast(bitcode.ptr);
985 file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err|989 file.writeStreamingAll(io, ptr[0..(bitcode.len * 4)]) catch |err|
986 return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) });990 return diags.fail("failed to write to '{s}': {t}", .{ path, err });
987 }991 }
988992
989 if (options.asm_path == null and options.bin_path == null and993 if (options.asm_path == null and options.bin_path == null and
990 options.post_ir_path == null and options.post_bc_path == null) return;994 options.post_ir_path == null and options.post_bc_path == null) return;
991995
992 if (options.post_bc_path) |path| {996 if (options.post_bc_path) |path| {
993 var file = std.fs.cwd().createFile(path, .{}) catch |err|997 var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err|
994 return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) });998 return diags.fail("failed to create '{s}': {t}", .{ path, err });
995 defer file.close();999 defer file.close(io);
9961000
997 const ptr: [*]const u8 = @ptrCast(bitcode.ptr);1001 const ptr: [*]const u8 = @ptrCast(bitcode.ptr);
998 file.writeAll(ptr[0..(bitcode.len * 4)]) catch |err|1002 file.writeStreamingAll(io, ptr[0..(bitcode.len * 4)]) catch |err|
999 return diags.fail("failed to write to '{s}': {s}", .{ path, @errorName(err) });1003 return diags.fail("failed to write to '{s}': {t}", .{ path, err });
1000 }1004 }
10011005
1002 if (!build_options.have_llvm or !comp.config.use_lib_llvm) {1006 if (!build_options.have_llvm or !comp.config.use_lib_llvm) {
...@@ -2710,7 +2714,7 @@ pub const Object = struct {...@@ -2710,7 +2714,7 @@ pub const Object = struct {
2710 }2714 }
27112715
2712 fn allocTypeName(o: *Object, pt: Zcu.PerThread, ty: Type) Allocator.Error![:0]const u8 {2716 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);
2714 defer aw.deinit();2718 defer aw.deinit();
2715 ty.print(&aw.writer, pt, null) catch |err| switch (err) {2719 ty.print(&aw.writer, pt, null) catch |err| switch (err) {
2716 error.WriteFailed => return error.OutOfMemory,2720 error.WriteFailed => return error.OutOfMemory,
src/crash_report.zig+7-6
...@@ -95,19 +95,20 @@ fn dumpCrashContext() Io.Writer.Error!void {...@@ -95,19 +95,20 @@ fn dumpCrashContext() Io.Writer.Error!void {
9595
96 // TODO: this does mean that a different thread could grab the stderr mutex between the context96 // TODO: this does mean that a different thread could grab the stderr mutex between the context
97 // and the actual panic printing, which would be quite confusing.97 // and the actual panic printing, which would be quite confusing.
98 const stderr, _ = std.debug.lockStderrWriter(&.{});98 const stderr = std.debug.lockStderr(&.{});
99 defer std.debug.unlockStderrWriter();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
103 if (CodegenFunc.current) |*cg| {104 if (CodegenFunc.current) |*cg| {
104 const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav;105 const func_nav = cg.zcu.funcInfo(cg.func_index).owner_nav;
105 const func_fqn = cg.zcu.intern_pool.getNav(func_nav).fqn;106 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)});
107 } else if (AnalyzeBody.current) |anal| {108 } else if (AnalyzeBody.current) |anal| {
108 try dumpCrashContextSema(anal, stderr, &S.crash_heap);109 try dumpCrashContextSema(anal, w, &S.crash_heap);
109 } else {110 } else {
110 try stderr.writeAll("(no context)\n\n");111 try w.writeAll("(no context)\n\n");
111 }112 }
112}113}
113fn dumpCrashContextSema(anal: *AnalyzeBody, stderr: *Io.Writer, crash_heap: []u8) Io.Writer.Error!void {114fn 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 {...@@ -37,9 +37,9 @@ const Fmt = struct {
37 arena: Allocator,37 arena: Allocator,
38 io: Io,38 io: Io,
39 out_buffer: std.Io.Writer.Allocating,39 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);
43};43};
4444
45pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void {45pub 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) !...@@ -59,8 +59,8 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
59 const arg = args[i];59 const arg = args[i];
60 if (mem.startsWith(u8, arg, "-")) {60 if (mem.startsWith(u8, arg, "-")) {
61 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {61 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
62 try fs.File.stdout().writeAll(usage_fmt);62 try Io.File.stdout().writeStreamingAll(io, usage_fmt);
63 return process.cleanExit();63 return process.cleanExit(io);
64 } else if (mem.eql(u8, arg, "--color")) {64 } else if (mem.eql(u8, arg, "--color")) {
65 if (i + 1 >= args.len) {65 if (i + 1 >= args.len) {
66 fatal("expected [auto|on|off] after --color", .{});66 fatal("expected [auto|on|off] after --color", .{});
...@@ -99,9 +99,9 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -99,9 +99,9 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
99 fatal("cannot use --stdin with positional arguments", .{});99 fatal("cannot use --stdin with positional arguments", .{});
100 }100 }
101101
102 const stdin: fs.File = .stdin();102 const stdin: Io.File = .stdin();
103 var stdio_buffer: [1024]u8 = undefined;103 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);
105 const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| {105 const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| {
106 fatal("unable to read stdin: {}", .{err});106 fatal("unable to read stdin: {}", .{err});
107 };107 };
...@@ -124,7 +124,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -124,7 +124,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
124 try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>");124 try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>");
125 var error_bundle = try wip_errors.toOwnedBundle("");125 var error_bundle = try wip_errors.toOwnedBundle("");
126 defer error_bundle.deinit(gpa);126 defer error_bundle.deinit(gpa);
127 error_bundle.renderToStdErr(.{}, color);127 error_bundle.renderToStderr(io, .{}, color) catch {};
128 process.exit(2);128 process.exit(2);
129 }129 }
130 } else {130 } else {
...@@ -138,12 +138,12 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -138,12 +138,12 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
138 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, "<stdin>");138 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, "<stdin>");
139 var error_bundle = try wip_errors.toOwnedBundle("");139 var error_bundle = try wip_errors.toOwnedBundle("");
140 defer error_bundle.deinit(gpa);140 defer error_bundle.deinit(gpa);
141 error_bundle.renderToStdErr(.{}, color);141 error_bundle.renderToStderr(io, .{}, color) catch {};
142 process.exit(2);142 process.exit(2);
143 }143 }
144 }144 }
145 } else if (tree.errors.len != 0) {145 } 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 {};
147 process.exit(2);147 process.exit(2);
148 }148 }
149 const formatted = try tree.renderAlloc(gpa);149 const formatted = try tree.renderAlloc(gpa);
...@@ -154,7 +154,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -154,7 +154,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
154 process.exit(code);154 process.exit(code);
155 }155 }
156156
157 return fs.File.stdout().writeAll(formatted);157 return Io.File.stdout().writeStreamingAll(io, formatted);
158 }158 }
159159
160 if (input_files.items.len == 0) {160 if (input_files.items.len == 0) {
...@@ -162,7 +162,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -162,7 +162,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
162 }162 }
163163
164 var stdout_buffer: [4096]u8 = undefined;164 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
167 var fmt: Fmt = .{167 var fmt: Fmt = .{
168 .gpa = gpa,168 .gpa = gpa,
...@@ -182,13 +182,13 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -182,13 +182,13 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
182 // Mark any excluded files/directories as already seen,182 // Mark any excluded files/directories as already seen,
183 // so that they are skipped later during actual processing183 // so that they are skipped later during actual processing
184 for (excluded_files.items) |file_path| {184 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) {
186 error.FileNotFound => continue,186 error.FileNotFound => continue,
187 // On Windows, statFile does not work for directories187 // On Windows, statFile does not work for directories
188 error.IsDir => dir: {188 error.IsDir => dir: {
189 var dir = try fs.cwd().openDir(file_path, .{});189 var dir = try Io.Dir.cwd().openDir(io, file_path, .{});
190 defer dir.close();190 defer dir.close(io);
191 break :dir try dir.stat();191 break :dir try dir.stat(io);
192 },192 },
193 else => |e| return e,193 else => |e| return e,
194 };194 };
...@@ -196,7 +196,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -196,7 +196,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
196 }196 }
197197
198 for (input_files.items) |file_path| {198 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);
200 }200 }
201 try fmt.stdout_writer.interface.flush();201 try fmt.stdout_writer.interface.flush();
202 if (fmt.any_error) {202 if (fmt.any_error) {
...@@ -204,7 +204,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -204,7 +204,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
204 }204 }
205}205}
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 {
208 fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {208 fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {
209 error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),209 error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),
210 else => {210 else => {
...@@ -219,17 +219,19 @@ fn fmtPathDir(...@@ -219,17 +219,19 @@ fn fmtPathDir(
219 fmt: *Fmt,219 fmt: *Fmt,
220 file_path: []const u8,220 file_path: []const u8,
221 check_mode: bool,221 check_mode: bool,
222 parent_dir: fs.Dir,222 parent_dir: Io.Dir,
223 parent_sub_path: []const u8,223 parent_sub_path: []const u8,
224) !void {224) !void {
225 var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true });225 const io = fmt.io;
226 defer dir.close();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);
229 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;231 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
230232
231 var dir_it = dir.iterate();233 var dir_it = dir.iterate();
232 while (try dir_it.next()) |entry| {234 while (try dir_it.next(io)) |entry| {
233 const is_dir = entry.kind == .directory;235 const is_dir = entry.kind == .directory;
234236
235 if (mem.startsWith(u8, entry.name, ".")) continue;237 if (mem.startsWith(u8, entry.name, ".")) continue;
...@@ -242,7 +244,7 @@ fn fmtPathDir(...@@ -242,7 +244,7 @@ fn fmtPathDir(
242 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);244 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
243 } else {245 } else {
244 fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {246 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 });
246 fmt.any_error = true;248 fmt.any_error = true;
247 return;249 return;
248 };250 };
...@@ -255,22 +257,22 @@ fn fmtPathFile(...@@ -255,22 +257,22 @@ fn fmtPathFile(
255 fmt: *Fmt,257 fmt: *Fmt,
256 file_path: []const u8,258 file_path: []const u8,
257 check_mode: bool,259 check_mode: bool,
258 dir: fs.Dir,260 dir: Io.Dir,
259 sub_path: []const u8,261 sub_path: []const u8,
260) !void {262) !void {
261 const io = fmt.io;263 const io = fmt.io;
262264
263 const source_file = try dir.openFile(sub_path, .{});265 const source_file = try dir.openFile(io, sub_path, .{});
264 var file_closed = false;266 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
269 if (stat.kind == .directory)271 if (stat.kind == .directory)
270 return error.IsDir;272 return error.IsDir;
271273
272 var read_buffer: [1024]u8 = undefined;274 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);
274 file_reader.size = stat.size;276 file_reader.size = stat.size;
275277
276 const gpa = fmt.gpa;278 const gpa = fmt.gpa;
...@@ -280,7 +282,7 @@ fn fmtPathFile(...@@ -280,7 +282,7 @@ fn fmtPathFile(
280 };282 };
281 defer gpa.free(source_code);283 defer gpa.free(source_code);
282284
283 source_file.close();285 source_file.close(io);
284 file_closed = true;286 file_closed = true;
285287
286 // Add to set after no longer possible to get error.IsDir.288 // Add to set after no longer possible to get error.IsDir.
...@@ -296,7 +298,7 @@ fn fmtPathFile(...@@ -296,7 +298,7 @@ fn fmtPathFile(
296 defer tree.deinit(gpa);298 defer tree.deinit(gpa);
297299
298 if (tree.errors.len != 0) {300 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);
300 fmt.any_error = true;302 fmt.any_error = true;
301 return;303 return;
302 }304 }
...@@ -317,7 +319,7 @@ fn fmtPathFile(...@@ -317,7 +319,7 @@ fn fmtPathFile(
317 try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path);319 try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path);
318 var error_bundle = try wip_errors.toOwnedBundle("");320 var error_bundle = try wip_errors.toOwnedBundle("");
319 defer error_bundle.deinit(gpa);321 defer error_bundle.deinit(gpa);
320 error_bundle.renderToStdErr(.{}, fmt.color);322 try error_bundle.renderToStderr(io, .{}, fmt.color);
321 fmt.any_error = true;323 fmt.any_error = true;
322 }324 }
323 },325 },
...@@ -332,7 +334,7 @@ fn fmtPathFile(...@@ -332,7 +334,7 @@ fn fmtPathFile(
332 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path);334 try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path);
333 var error_bundle = try wip_errors.toOwnedBundle("");335 var error_bundle = try wip_errors.toOwnedBundle("");
334 defer error_bundle.deinit(gpa);336 defer error_bundle.deinit(gpa);
335 error_bundle.renderToStdErr(.{}, fmt.color);337 try error_bundle.renderToStderr(io, .{}, fmt.color);
336 fmt.any_error = true;338 fmt.any_error = true;
337 }339 }
338 },340 },
...@@ -353,7 +355,7 @@ fn fmtPathFile(...@@ -353,7 +355,7 @@ fn fmtPathFile(
353 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});355 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});
354 fmt.any_error = true;356 fmt.any_error = true;
355 } else {357 } 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 = &.{} });
357 defer af.deinit();359 defer af.deinit();
358360
359 try af.file_writer.interface.writeAll(fmt.out_buffer.written());361 try af.file_writer.interface.writeAll(fmt.out_buffer.written());
...@@ -368,7 +370,7 @@ pub fn main() !void {...@@ -368,7 +370,7 @@ pub fn main() !void {
368 var arena_instance = std.heap.ArenaAllocator.init(gpa);370 var arena_instance = std.heap.ArenaAllocator.init(gpa);
369 const arena = arena_instance.allocator();371 const arena = arena_instance.allocator();
370 const args = try process.argsAlloc(arena);372 const args = try process.argsAlloc(arena);
371 var threaded: std.Io.Threaded = .init(gpa);373 var threaded: std.Io.Threaded = .init(gpa, .{});
372 defer threaded.deinit();374 defer threaded.deinit();
373 const io = threaded.io();375 const io = threaded.io();
374 return run(gpa, arena, io, args[1..]);376 return run(gpa, arena, io, args[1..]);
src/introspect.zig+50-48
...@@ -1,53 +1,55 @@...@@ -1,53 +1,55 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2const build_options = @import("build_options");
3
4const std = @import("std");
5const Io = std.Io;
6const Dir = std.Io.Dir;
3const mem = std.mem;7const mem = std.mem;
4const Allocator = mem.Allocator;8const Allocator = std.mem.Allocator;
5const os = std.os;
6const fs = std.fs;
7const Cache = std.Build.Cache;9const Cache = std.Build.Cache;
10
8const Compilation = @import("Compilation.zig");11const Compilation = @import("Compilation.zig");
9const Package = @import("Package.zig");12const Package = @import("Package.zig");
10const build_options = @import("build_options");
1113
12/// Returns the sub_path that worked, or `null` if none did.14/// Returns the sub_path that worked, or `null` if none did.
13/// The path of the returned Directory is relative to `base`.15/// The path of the returned Directory is relative to `base`.
14/// The handle of the returned Directory is open.16/// The handle of the returned Directory is open.
15fn testZigInstallPrefix(base_dir: fs.Dir) ?Cache.Directory {17fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory {
16 const test_index_file = "std" ++ fs.path.sep_str ++ "std.zig";18 const test_index_file = "std" ++ Dir.path.sep_str ++ "std.zig";
1719
18 zig_dir: {20 zig_dir: {
19 // Try lib/zig/std/std.zig21 // Try lib/zig/std/std.zig
20 const lib_zig = "lib" ++ fs.path.sep_str ++ "zig";22 const lib_zig = "lib" ++ Dir.path.sep_str ++ "zig";
21 var test_zig_dir = base_dir.openDir(lib_zig, .{}) catch break :zig_dir;23 var test_zig_dir = base_dir.openDir(io, lib_zig, .{}) catch break :zig_dir;
22 const file = test_zig_dir.openFile(test_index_file, .{}) catch {24 const file = test_zig_dir.openFile(io, test_index_file, .{}) catch {
23 test_zig_dir.close();25 test_zig_dir.close(io);
24 break :zig_dir;26 break :zig_dir;
25 };27 };
26 file.close();28 file.close(io);
27 return .{ .handle = test_zig_dir, .path = lib_zig };29 return .{ .handle = test_zig_dir, .path = lib_zig };
28 }30 }
2931
30 // Try lib/std/std.zig32 // Try lib/std/std.zig
31 var test_zig_dir = base_dir.openDir("lib", .{}) catch return null;33 var test_zig_dir = base_dir.openDir(io, "lib", .{}) catch return null;
32 const file = test_zig_dir.openFile(test_index_file, .{}) catch {34 const file = test_zig_dir.openFile(io, test_index_file, .{}) catch {
33 test_zig_dir.close();35 test_zig_dir.close(io);
34 return null;36 return null;
35 };37 };
36 file.close();38 file.close(io);
37 return .{ .handle = test_zig_dir, .path = "lib" };39 return .{ .handle = test_zig_dir, .path = "lib" };
38}40}
3941
40/// Both the directory handle and the path are newly allocated resources which the caller now owns.42/// 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 {
42 const cwd_path = try getResolvedCwd(gpa);44 const cwd_path = try getResolvedCwd(gpa);
43 defer gpa.free(cwd_path);45 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);
45 defer gpa.free(self_exe_path);47 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);
48}50}
4951
50/// Like `std.process.getCwdAlloc`, but also resolves the path with `std.fs.path.resolve`. This52/// Like `std.process.getCwdAlloc`, but also resolves the path with `Dir.path.resolve`. This
51/// means the path has no repeated separators, no "." or ".." components, and no trailing separator.53/// means the path has no repeated separators, no "." or ".." components, and no trailing separator.
52/// On WASI, "" is returned instead of ".".54/// On WASI, "" is returned instead of ".".
53pub fn getResolvedCwd(gpa: Allocator) error{55pub fn getResolvedCwd(gpa: Allocator) error{
...@@ -65,27 +67,28 @@ pub fn getResolvedCwd(gpa: Allocator) error{...@@ -65,27 +67,28 @@ pub fn getResolvedCwd(gpa: Allocator) error{
65 }67 }
66 const cwd = try std.process.getCwdAlloc(gpa);68 const cwd = try std.process.getCwdAlloc(gpa);
67 defer gpa.free(cwd);69 defer gpa.free(cwd);
68 const resolved = try fs.path.resolve(gpa, &.{cwd});70 const resolved = try Dir.path.resolve(gpa, &.{cwd});
69 std.debug.assert(fs.path.isAbsolute(resolved));71 std.debug.assert(Dir.path.isAbsolute(resolved));
70 return resolved;72 return resolved;
71}73}
7274
73/// Both the directory handle and the path are newly allocated resources which the caller now owns.75/// Both the directory handle and the path are newly allocated resources which the caller now owns.
74pub fn findZigLibDirFromSelfExe(76pub fn findZigLibDirFromSelfExe(
75 allocator: Allocator,77 allocator: Allocator,
78 io: Io,
76 /// The return value of `getResolvedCwd`.79 /// The return value of `getResolvedCwd`.
77 /// Passed as an argument to avoid pointlessly repeating the call.80 /// Passed as an argument to avoid pointlessly repeating the call.
78 cwd_path: []const u8,81 cwd_path: []const u8,
79 self_exe_path: []const u8,82 self_exe_path: []const u8,
80) error{ OutOfMemory, FileNotFound }!Cache.Directory {83) error{ OutOfMemory, FileNotFound }!Cache.Directory {
81 const cwd = fs.cwd();84 const cwd = Io.Dir.cwd();
82 var cur_path: []const u8 = self_exe_path;85 var cur_path: []const u8 = self_exe_path;
83 while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) {86 while (Dir.path.dirname(cur_path)) |dirname| : (cur_path = dirname) {
84 var base_dir = cwd.openDir(dirname, .{}) catch continue;87 var base_dir = cwd.openDir(io, dirname, .{}) catch continue;
85 defer base_dir.close();88 defer base_dir.close(io);
8689
87 const sub_directory = testZigInstallPrefix(base_dir) orelse continue;90 const sub_directory = testZigInstallPrefix(io, base_dir) orelse continue;
88 const p = try fs.path.join(allocator, &.{ dirname, sub_directory.path.? });91 const p = try Dir.path.join(allocator, &.{ dirname, sub_directory.path.? });
89 defer allocator.free(p);92 defer allocator.free(p);
9093
91 const resolved = try resolvePath(allocator, cwd_path, &.{p});94 const resolved = try resolvePath(allocator, cwd_path, &.{p});
...@@ -109,18 +112,18 @@ pub fn resolveGlobalCacheDir(allocator: Allocator) ![]u8 {...@@ -109,18 +112,18 @@ pub fn resolveGlobalCacheDir(allocator: Allocator) ![]u8 {
109 if (builtin.os.tag != .windows) {112 if (builtin.os.tag != .windows) {
110 if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| {113 if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| {
111 if (cache_root.len > 0) {114 if (cache_root.len > 0) {
112 return fs.path.join(allocator, &.{ cache_root, appname });115 return Dir.path.join(allocator, &.{ cache_root, appname });
113 }116 }
114 }117 }
115 if (std.zig.EnvVar.HOME.getPosix()) |home| {118 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 });
117 }120 }
118 }121 }
119122
120 return fs.getAppDataDir(allocator, appname);123 return std.fs.getAppDataDir(allocator, appname);
121}124}
122125
123/// Similar to `fs.path.resolve`, but converts to a cwd-relative path, or, if that would126/// Similar to `Dir.path.resolve`, but converts to a cwd-relative path, or, if that would
124/// start with a relative up-dir (".."), an absolute path based on the cwd. Also, the cwd127/// start with a relative up-dir (".."), an absolute path based on the cwd. Also, the cwd
125/// returns the empty string ("") instead of ".".128/// returns the empty string ("") instead of ".".
126pub fn resolvePath(129pub fn resolvePath(
...@@ -132,7 +135,7 @@ pub fn resolvePath(...@@ -132,7 +135,7 @@ pub fn resolvePath(
132) Allocator.Error![]u8 {135) Allocator.Error![]u8 {
133 if (builtin.target.os.tag == .wasi) {136 if (builtin.target.os.tag == .wasi) {
134 std.debug.assert(mem.eql(u8, cwd_resolved, ""));137 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);
136 if (mem.eql(u8, res, ".")) {139 if (mem.eql(u8, res, ".")) {
137 gpa.free(res);140 gpa.free(res);
138 return "";141 return "";
...@@ -142,16 +145,16 @@ pub fn resolvePath(...@@ -142,16 +145,16 @@ pub fn resolvePath(
142145
143 // Heuristic for a fast path: if no component is absolute and ".." never appears, we just need to resolve `paths`.146 // Heuristic for a fast path: if no component is absolute and ".." never appears, we just need to resolve `paths`.
144 for (paths) |p| {147 for (paths) |p| {
145 if (fs.path.isAbsolute(p)) break; // absolute path148 if (Dir.path.isAbsolute(p)) break; // absolute path
146 if (mem.indexOf(u8, p, "..") != null) break; // may contain up-dir149 if (mem.indexOf(u8, p, "..") != null) break; // may contain up-dir
147 } else {150 } else {
148 // no absolute path, no "..".151 // no absolute path, no "..".
149 const res = try fs.path.resolve(gpa, paths);152 const res = try Dir.path.resolve(gpa, paths);
150 if (mem.eql(u8, res, ".")) {153 if (mem.eql(u8, res, ".")) {
151 gpa.free(res);154 gpa.free(res);
152 return "";155 return "";
153 }156 }
154 std.debug.assert(!fs.path.isAbsolute(res));157 std.debug.assert(!Dir.path.isAbsolute(res));
155 std.debug.assert(!isUpDir(res));158 std.debug.assert(!isUpDir(res));
156 return res;159 return res;
157 }160 }
...@@ -160,19 +163,19 @@ pub fn resolvePath(...@@ -160,19 +163,19 @@ pub fn resolvePath(
160 // Optimization: `paths` often has just one element.163 // Optimization: `paths` often has just one element.
161 const path_resolved = switch (paths.len) {164 const path_resolved = switch (paths.len) {
162 0 => unreachable,165 0 => unreachable,
163 1 => try fs.path.resolve(gpa, &.{ cwd_resolved, paths[0] }),166 1 => try Dir.path.resolve(gpa, &.{ cwd_resolved, paths[0] }),
164 else => r: {167 else => r: {
165 const all_paths = try gpa.alloc([]const u8, paths.len + 1);168 const all_paths = try gpa.alloc([]const u8, paths.len + 1);
166 defer gpa.free(all_paths);169 defer gpa.free(all_paths);
167 all_paths[0] = cwd_resolved;170 all_paths[0] = cwd_resolved;
168 @memcpy(all_paths[1..], paths);171 @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);
170 },173 },
171 };174 };
172 errdefer gpa.free(path_resolved);175 errdefer gpa.free(path_resolved);
173176
174 std.debug.assert(fs.path.isAbsolute(path_resolved));177 std.debug.assert(Dir.path.isAbsolute(path_resolved));
175 std.debug.assert(fs.path.isAbsolute(cwd_resolved));178 std.debug.assert(Dir.path.isAbsolute(cwd_resolved));
176179
177 if (!std.mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd180 if (!std.mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd
178 if (path_resolved.len == cwd_resolved.len) {181 if (path_resolved.len == cwd_resolved.len) {
...@@ -180,7 +183,7 @@ pub fn resolvePath(...@@ -180,7 +183,7 @@ pub fn resolvePath(
180 gpa.free(path_resolved);183 gpa.free(path_resolved);
181 return "";184 return "";
182 }185 }
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
185 // in cwd; extract sub path188 // in cwd; extract sub path
186 const sub_path = try gpa.dupe(u8, path_resolved[cwd_resolved.len + 1 ..]);189 const sub_path = try gpa.dupe(u8, path_resolved[cwd_resolved.len + 1 ..]);
...@@ -188,9 +191,8 @@ pub fn resolvePath(...@@ -188,9 +191,8 @@ pub fn resolvePath(
188 return sub_path;191 return sub_path;
189}192}
190193
191/// TODO move this to std.fs.path
192pub fn isUpDir(p: []const u8) bool {194pub 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);
194}196}
195197
196pub const default_local_zig_cache_basename = ".zig-cache";198pub const default_local_zig_cache_basename = ".zig-cache";
...@@ -198,15 +200,15 @@ pub const default_local_zig_cache_basename = ".zig-cache";...@@ -198,15 +200,15 @@ pub const default_local_zig_cache_basename = ".zig-cache";
198/// Searches upwards from `cwd` for a directory containing a `build.zig` file.200/// Searches upwards from `cwd` for a directory containing a `build.zig` file.
199/// If such a directory is found, returns the path to it joined to the `.zig_cache` name.201/// If such a directory is found, returns the path to it joined to the `.zig_cache` name.
200/// Otherwise, returns `null`, indicating no suitable local cache location.202/// 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 {
202 var cur_dir = cwd;204 var cur_dir = cwd;
203 while (true) {205 while (true) {
204 const joined = try fs.path.join(arena, &.{ cur_dir, Package.build_zig_basename });206 const joined = try Dir.path.join(arena, &.{ cur_dir, Package.build_zig_basename });
205 if (fs.cwd().access(joined, .{})) |_| {207 if (Io.Dir.cwd().access(io, joined, .{})) |_| {
206 return try fs.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename });208 return try Dir.path.join(arena, &.{ cur_dir, default_local_zig_cache_basename });
207 } else |err| switch (err) {209 } else |err| switch (err) {
208 error.FileNotFound => {210 error.FileNotFound => {
209 cur_dir = fs.path.dirname(cur_dir) orelse return null;211 cur_dir = Dir.path.dirname(cur_dir) orelse return null;
210 continue;212 continue;
211 },213 },
212 else => return null,214 else => return null,
src/libs/freebsd.zig+14-14
...@@ -1,9 +1,9 @@...@@ -1,9 +1,9 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
3const mem = std.mem;4const mem = std.mem;
4const log = std.log;5const log = std.log;
5const fs = std.fs;6const path = std.Io.Dir.path;
6const path = fs.path;
7const assert = std.debug.assert;7const assert = std.debug.assert;
8const Version = std.SemanticVersion;8const Version = std.SemanticVersion;
9const Path = std.Build.Cache.Path;9const Path = std.Build.Cache.Path;
...@@ -401,8 +401,8 @@ pub const BuiltSharedObjects = struct {...@@ -401,8 +401,8 @@ pub const BuiltSharedObjects = struct {
401 lock: Cache.Lock,401 lock: Cache.Lock,
402 dir_path: Path,402 dir_path: Path,
403403
404 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void {404 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void {
405 self.lock.release();405 self.lock.release(io);
406 gpa.free(self.dir_path.sub_path);406 gpa.free(self.dir_path.sub_path);
407 self.* = undefined;407 self.* = undefined;
408 }408 }
...@@ -444,12 +444,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -444,12 +444,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
444 var cache: Cache = .{444 var cache: Cache = .{
445 .gpa = gpa,445 .gpa = gpa,
446 .io = io,446 .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", .{}),
448 };448 };
449 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });449 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
450 cache.addPrefix(comp.dirs.zig_lib);450 cache.addPrefix(comp.dirs.zig_lib);
451 cache.addPrefix(comp.dirs.global_cache);451 cache.addPrefix(comp.dirs.global_cache);
452 defer cache.manifest_dir.close();452 defer cache.manifest_dir.close(io);
453453
454 var man = cache.obtain();454 var man = cache.obtain();
455 defer man.deinit();455 defer man.deinit();
...@@ -468,7 +468,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -468,7 +468,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
468 .lock = man.toOwnedLock(),468 .lock = man.toOwnedLock(),
469 .dir_path = .{469 .dir_path = .{
470 .root_dir = comp.dirs.global_cache,470 .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),
472 },472 },
473 });473 });
474 }474 }
...@@ -477,10 +477,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -477,10 +477,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
477 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });477 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
478478
479 var o_directory: Cache.Directory = .{479 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, .{}),
481 .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),481 .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),
482 };482 };
483 defer o_directory.handle.close();483 defer o_directory.handle.close(io);
484484
485 const abilists_contents = man.files.keys()[abilists_index].contents.?;485 const abilists_contents = man.files.keys()[abilists_index].contents.?;
486 const metadata = try loadMetaData(gpa, abilists_contents);486 const metadata = try loadMetaData(gpa, abilists_contents);
...@@ -520,7 +520,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -520,7 +520,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
520 for (metadata.all_versions[0 .. target_ver_index + 1]) |ver| {520 for (metadata.all_versions[0 .. target_ver_index + 1]) |ver| {
521 try map_contents.print("FBSD_{d}.{d} {{ }};\n", .{ ver.major, ver.minor });521 try map_contents.print("FBSD_{d}.{d} {{ }};\n", .{ ver.major, ver.minor });
522 }522 }
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 });
524 map_contents.deinit();524 map_contents.deinit();
525 }525 }
526526
...@@ -974,7 +974,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -974,7 +974,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
974974
975 var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "stdthreads", etc.975 var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "stdthreads", etc.
976 const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;976 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 });
978 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);978 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
979 }979 }
980980
...@@ -986,7 +986,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -986,7 +986,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
986 .lock = man.toOwnedLock(),986 .lock = man.toOwnedLock(),
987 .dir_path = .{987 .dir_path = .{
988 .root_dir = comp.dirs.global_cache,988 .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),
990 },990 },
991 });991 });
992}992}
...@@ -1014,7 +1014,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C...@@ -1014,7 +1014,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C
1014 const so_path: Path = .{1014 const so_path: Path = .{
1015 .root_dir = so_files.dir_path.root_dir,1015 .root_dir = so_files.dir_path.root_dir,
1016 .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{1016 .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),
1018 }) catch return comp.setAllocFailure(),1018 }) catch return comp.setAllocFailure(),
1019 };1019 };
1020 task_buffer[task_buffer_i] = .{ .load_dso = so_path };1020 task_buffer[task_buffer_i] = .{ .load_dso = so_path };
src/libs/glibc.zig+16-16
...@@ -1,9 +1,9 @@...@@ -1,9 +1,9 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
3const mem = std.mem;4const mem = std.mem;
4const log = std.log;5const log = std.log;
5const fs = std.fs;6const path = std.Io.Dir.path;
6const path = fs.path;
7const assert = std.debug.assert;7const assert = std.debug.assert;
8const Version = std.SemanticVersion;8const Version = std.SemanticVersion;
9const Path = std.Build.Cache.Path;9const Path = std.Build.Cache.Path;
...@@ -640,8 +640,8 @@ pub const BuiltSharedObjects = struct {...@@ -640,8 +640,8 @@ pub const BuiltSharedObjects = struct {
640 lock: Cache.Lock,640 lock: Cache.Lock,
641 dir_path: Path,641 dir_path: Path,
642642
643 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void {643 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void {
644 self.lock.release();644 self.lock.release(io);
645 gpa.free(self.dir_path.sub_path);645 gpa.free(self.dir_path.sub_path);
646 self.* = undefined;646 self.* = undefined;
647 }647 }
...@@ -679,12 +679,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -679,12 +679,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
679 var cache: Cache = .{679 var cache: Cache = .{
680 .gpa = gpa,680 .gpa = gpa,
681 .io = io,681 .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", .{}),
683 };683 };
684 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });684 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
685 cache.addPrefix(comp.dirs.zig_lib);685 cache.addPrefix(comp.dirs.zig_lib);
686 cache.addPrefix(comp.dirs.global_cache);686 cache.addPrefix(comp.dirs.global_cache);
687 defer cache.manifest_dir.close();687 defer cache.manifest_dir.close(io);
688688
689 var man = cache.obtain();689 var man = cache.obtain();
690 defer man.deinit();690 defer man.deinit();
...@@ -703,7 +703,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -703,7 +703,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
703 .lock = man.toOwnedLock(),703 .lock = man.toOwnedLock(),
704 .dir_path = .{704 .dir_path = .{
705 .root_dir = comp.dirs.global_cache,705 .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),
707 },707 },
708 });708 });
709 }709 }
...@@ -712,10 +712,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -712,10 +712,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
712 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });712 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
713713
714 var o_directory: Cache.Directory = .{714 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, .{}),
716 .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),716 .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),
717 };717 };
718 defer o_directory.handle.close();718 defer o_directory.handle.close(io);
719719
720 const abilists_contents = man.files.keys()[abilists_index].contents.?;720 const abilists_contents = man.files.keys()[abilists_index].contents.?;
721 const metadata = try loadMetaData(gpa, abilists_contents);721 const metadata = try loadMetaData(gpa, abilists_contents);
...@@ -759,7 +759,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -759,7 +759,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
759 try map_contents.print("GLIBC_{d}.{d}.{d} {{ }};\n", .{ ver.major, ver.minor, ver.patch });759 try map_contents.print("GLIBC_{d}.{d}.{d} {{ }};\n", .{ ver.major, ver.minor, ver.patch });
760 }760 }
761 }761 }
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 });
763 map_contents.deinit(); // The most recent allocation of an arena can be freed :)763 map_contents.deinit(); // The most recent allocation of an arena can be freed :)
764 }764 }
765765
...@@ -775,7 +775,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -775,7 +775,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
775 try stubs_asm.appendSlice(".text\n");775 try stubs_asm.appendSlice(".text\n");
776776
777 var sym_i: usize = 0;777 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);
779 var opt_symbol_name: ?[]const u8 = null;779 var opt_symbol_name: ?[]const u8 = null;
780 var versions_buffer: [32]u8 = undefined;780 var versions_buffer: [32]u8 = undefined;
781 var versions_len: usize = undefined;781 var versions_len: usize = undefined;
...@@ -796,7 +796,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -796,7 +796,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
796 // twice, which causes a "duplicate symbol" assembler error.796 // twice, which causes a "duplicate symbol" assembler error.
797 var versions_written = std.AutoArrayHashMap(Version, void).init(arena);797 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
801 const fn_inclusions_len = try inc_reader.takeInt(u16, .little);801 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...@@ -1118,7 +1118,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
11181118
1119 var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc.1119 var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc.
1120 const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;1120 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 });
1122 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);1122 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
1123 }1123 }
11241124
...@@ -1130,7 +1130,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -1130,7 +1130,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
1130 .lock = man.toOwnedLock(),1130 .lock = man.toOwnedLock(),
1131 .dir_path = .{1131 .dir_path = .{
1132 .root_dir = comp.dirs.global_cache,1132 .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),
1134 },1134 },
1135 });1135 });
1136}1136}
...@@ -1156,7 +1156,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C...@@ -1156,7 +1156,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C
1156 const so_path: Path = .{1156 const so_path: Path = .{
1157 .root_dir = so_files.dir_path.root_dir,1157 .root_dir = so_files.dir_path.root_dir,
1158 .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{1158 .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,
1160 }) catch return comp.setAllocFailure(),1160 }) catch return comp.setAllocFailure(),
1161 };1161 };
1162 task_buffer[task_buffer_i] = .{ .load_dso = so_path };1162 task_buffer[task_buffer_i] = .{ .load_dso = so_path };
src/libs/mingw.zig+39-26
...@@ -1,7 +1,8 @@...@@ -1,7 +1,8 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
3const mem = std.mem;4const mem = std.mem;
4const path = std.fs.path;5const path = std.Io.Dir.path;
5const assert = std.debug.assert;6const assert = std.debug.assert;
6const log = std.log.scoped(.mingw);7const log = std.log.scoped(.mingw);
78
...@@ -241,7 +242,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -241,7 +242,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
241 defer arena_allocator.deinit();242 defer arena_allocator.deinit();
242 const arena = arena_allocator.allocator();243 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) {
245 error.FileNotFound => {246 error.FileNotFound => {
246 log.debug("no {s}.def file available to make a DLL import {s}.lib", .{ lib_name, lib_name });247 log.debug("no {s}.def file available to make a DLL import {s}.lib", .{ lib_name, lib_name });
247 // In this case we will end up putting foo.lib onto the linker line and letting the linker248 // 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 {...@@ -257,12 +258,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
257 var cache: Cache = .{258 var cache: Cache = .{
258 .gpa = gpa,259 .gpa = gpa,
259 .io = io,260 .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", .{}),
261 };262 };
262 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });263 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
263 cache.addPrefix(comp.dirs.zig_lib);264 cache.addPrefix(comp.dirs.zig_lib);
264 cache.addPrefix(comp.dirs.global_cache);265 cache.addPrefix(comp.dirs.global_cache);
265 defer cache.manifest_dir.close();266 defer cache.manifest_dir.close(io);
266267
267 cache.hash.addBytes(build_options.version);268 cache.hash.addBytes(build_options.version);
268 cache.hash.addOptionalBytes(comp.dirs.zig_lib.path);269 cache.hash.addOptionalBytes(comp.dirs.zig_lib.path);
...@@ -296,26 +297,32 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -296,26 +297,32 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
296297
297 const digest = man.final();298 const digest = man.final();
298 const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest });299 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 var o_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, o_sub_path, .{});
300 defer o_dir.close();301 defer o_dir.close(io);
301302
302 const aro = @import("aro");303 const aro = @import("aro");
303 var diagnostics: aro.Diagnostics = .{304 var diagnostics: aro.Diagnostics = .{
304 .output = .{ .to_list = .{ .arena = .init(gpa) } },305 .output = .{ .to_list = .{ .arena = .init(gpa) } },
305 };306 };
306 defer diagnostics.deinit();307 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());
308 defer aro_comp.deinit();309 defer aro_comp.deinit();
309310
310 aro_comp.target = .fromZigTarget(target.*);311 aro_comp.target = .fromZigTarget(target.*);
311312
312 const include_dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "mingw", "def-include" });313 const include_dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "mingw", "def-include" });
313314
314 if (comp.verbose_cc) print: {315 if (comp.verbose_cc) {
315 var stderr, _ = std.debug.lockStderrWriter(&.{});316 var buffer: [256]u8 = undefined;
316 defer std.debug.unlockStderrWriter();317 const stderr = try io.lockStderr(&buffer, null);
317 nosuspend stderr.print("def file: {s}\n", .{def_file_path}) catch break :print;318 defer io.unlockStderr();
318 nosuspend stderr.print("include dir: {s}\n", .{include_dir}) catch break :print;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 };
319 }326 }
320327
321 try aro_comp.search_path.append(gpa, .{ .path = include_dir, .kind = .normal });328 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 {...@@ -332,18 +339,21 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
332339
333 if (aro_comp.diagnostics.output.to_list.messages.items.len != 0) {340 if (aro_comp.diagnostics.output.to_list.messages.items.len != 0) {
334 var buffer: [64]u8 = undefined;341 var buffer: [64]u8 = undefined;
335 const w, const ttyconf = std.debug.lockStderrWriter(&buffer);342 const stderr = try io.lockStderr(&buffer, null);
336 defer std.debug.unlockStderrWriter();343 defer io.unlockStderr();
337 for (aro_comp.diagnostics.output.to_list.messages.items) |msg| {344 for (aro_comp.diagnostics.output.to_list.messages.items) |msg| {
338 if (msg.kind == .@"fatal error" or msg.kind == .@"error") {345 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 };
340 return error.AroPreprocessorFailed;350 return error.AroPreprocessorFailed;
341 }351 }
342 }352 }
343 }353 }
344354
345 const members = members: {355 const members = members: {
346 var aw: std.Io.Writer.Allocating = .init(gpa);356 var aw: Io.Writer.Allocating = .init(gpa);
347 errdefer aw.deinit();357 errdefer aw.deinit();
348 try pp.prettyPrintTokens(&aw.writer, .result_only);358 try pp.prettyPrintTokens(&aw.writer, .result_only);
349359
...@@ -356,8 +366,9 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -356,8 +366,9 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
356 error.OutOfMemory => |e| return e,366 error.OutOfMemory => |e| return e,
357 error.ParseError => {367 error.ParseError => {
358 var buffer: [64]u8 = undefined;368 var buffer: [64]u8 = undefined;
359 const w, _ = std.debug.lockStderrWriter(&buffer);369 const stderr = try io.lockStderr(&buffer, null);
360 defer std.debug.unlockStderrWriter();370 defer io.unlockStderr();
371 const w = &stderr.file_writer.interface;
361 try w.writeAll("error: ");372 try w.writeAll("error: ");
362 try def_diagnostics.writeMsg(w, input);373 try def_diagnostics.writeMsg(w, input);
363 try w.writeByte('\n');374 try w.writeByte('\n');
...@@ -376,10 +387,10 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -376,10 +387,10 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
376 errdefer gpa.free(lib_final_path);387 errdefer gpa.free(lib_final_path);
377388
378 {389 {
379 const lib_final_file = try o_dir.createFile(final_lib_basename, .{ .truncate = true });390 const lib_final_file = try o_dir.createFile(io, final_lib_basename, .{ .truncate = true });
380 defer lib_final_file.close();391 defer lib_final_file.close(io);
381 var buffer: [1024]u8 = undefined;392 var buffer: [1024]u8 = undefined;
382 var file_writer = lib_final_file.writer(&buffer);393 var file_writer = lib_final_file.writer(io, &buffer);
383 try implib.writeCoffArchive(gpa, &file_writer.interface, members);394 try implib.writeCoffArchive(gpa, &file_writer.interface, members);
384 try file_writer.interface.flush();395 try file_writer.interface.flush();
385 }396 }
...@@ -401,11 +412,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -401,11 +412,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
401412
402pub fn libExists(413pub fn libExists(
403 allocator: Allocator,414 allocator: Allocator,
415 io: Io,
404 target: *const std.Target,416 target: *const std.Target,
405 zig_lib_directory: Cache.Directory,417 zig_lib_directory: Cache.Directory,
406 lib_name: []const u8,418 lib_name: []const u8,
407) !bool {419) !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) {
409 error.FileNotFound => return false,421 error.FileNotFound => return false,
410 else => |e| return e,422 else => |e| return e,
411 };423 };
...@@ -417,6 +429,7 @@ pub fn libExists(...@@ -417,6 +429,7 @@ pub fn libExists(
417/// see if a .def file exists.429/// see if a .def file exists.
418fn findDef(430fn findDef(
419 allocator: Allocator,431 allocator: Allocator,
432 io: Io,
420 target: *const std.Target,433 target: *const std.Target,
421 zig_lib_directory: Cache.Directory,434 zig_lib_directory: Cache.Directory,
422 lib_name: []const u8,435 lib_name: []const u8,
...@@ -442,7 +455,7 @@ fn findDef(...@@ -442,7 +455,7 @@ fn findDef(
442 } else {455 } else {
443 try override_path.print(fmt_path, .{ lib_path, lib_name });456 try override_path.print(fmt_path, .{ lib_path, lib_name });
444 }457 }
445 if (std.fs.cwd().access(override_path.items, .{})) |_| {458 if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| {
446 return override_path.toOwnedSlice();459 return override_path.toOwnedSlice();
447 } else |err| switch (err) {460 } else |err| switch (err) {
448 error.FileNotFound => {},461 error.FileNotFound => {},
...@@ -459,7 +472,7 @@ fn findDef(...@@ -459,7 +472,7 @@ fn findDef(
459 } else {472 } else {
460 try override_path.print(fmt_path, .{lib_name});473 try override_path.print(fmt_path, .{lib_name});
461 }474 }
462 if (std.fs.cwd().access(override_path.items, .{})) |_| {475 if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| {
463 return override_path.toOwnedSlice();476 return override_path.toOwnedSlice();
464 } else |err| switch (err) {477 } else |err| switch (err) {
465 error.FileNotFound => {},478 error.FileNotFound => {},
...@@ -476,7 +489,7 @@ fn findDef(...@@ -476,7 +489,7 @@ fn findDef(
476 } else {489 } else {
477 try override_path.print(fmt_path, .{lib_name});490 try override_path.print(fmt_path, .{lib_name});
478 }491 }
479 if (std.fs.cwd().access(override_path.items, .{})) |_| {492 if (Io.Dir.cwd().access(io, override_path.items, .{})) |_| {
480 return override_path.toOwnedSlice();493 return override_path.toOwnedSlice();
481 } else |err| switch (err) {494 } else |err| switch (err) {
482 error.FileNotFound => {},495 error.FileNotFound => {},
src/libs/mingw/def.zig+22-10
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
23
3pub const ModuleDefinitionType = enum {4pub const ModuleDefinitionType = enum {
4 mingw,5 mingw,
...@@ -663,7 +664,9 @@ test parse {...@@ -663,7 +664,9 @@ test parse {
663 \\664 \\
664 ;665 ;
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{
667 .{670 .{
668 .name = "foo",671 .name = "foo",
669 .mangled_symbol_name = null,672 .mangled_symbol_name = null,
...@@ -743,7 +746,7 @@ test parse {...@@ -743,7 +746,7 @@ test parse {
743 },746 },
744 });747 });
745748
746 try testParse(.I386, source, "foo.dll", &[_]ModuleDefinition.Export{749 try testParse(io, .I386, source, "foo.dll", &[_]ModuleDefinition.Export{
747 .{750 .{
748 .name = "_foo",751 .name = "_foo",
749 .mangled_symbol_name = null,752 .mangled_symbol_name = null,
...@@ -823,7 +826,7 @@ test parse {...@@ -823,7 +826,7 @@ test parse {
823 },826 },
824 });827 });
825828
826 try testParse(.ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{829 try testParse(io, .ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{
827 .{830 .{
828 .name = "foo",831 .name = "foo",
829 .mangled_symbol_name = null,832 .mangled_symbol_name = null,
...@@ -903,7 +906,7 @@ test parse {...@@ -903,7 +906,7 @@ test parse {
903 },906 },
904 });907 });
905908
906 try testParse(.ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{909 try testParse(io, .ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{
907 .{910 .{
908 .name = "foo",911 .name = "foo",
909 .mangled_symbol_name = null,912 .mangled_symbol_name = null,
...@@ -997,7 +1000,9 @@ test "ntdll" {...@@ -997,7 +1000,9 @@ test "ntdll" {
997 \\RtlActivateActivationContextUnsafeFast@01000 \\RtlActivateActivationContextUnsafeFast@0
998 ;1001 ;
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{
1001 .{1006 .{
1002 .name = "RtlDispatchAPC@12",1007 .name = "RtlDispatchAPC@12",
1003 .mangled_symbol_name = null,1008 .mangled_symbol_name = null,
...@@ -1023,15 +1028,22 @@ test "ntdll" {...@@ -1023,15 +1028,22 @@ test "ntdll" {
1023 });1028 });
1024}1029}
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 {
1027 var diagnostics: Diagnostics = undefined;1038 var diagnostics: Diagnostics = undefined;
1028 const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {1039 const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {
1029 error.OutOfMemory => |e| return e,1040 error.OutOfMemory => |e| return e,
1030 error.ParseError => {1041 error.ParseError => {
1031 const stderr, _ = std.debug.lockStderrWriter(&.{});1042 const stderr = try io.lockStderr(&.{}, null);
1032 defer std.debug.unlockStderrWriter();1043 defer io.unlockStderr();
1033 try diagnostics.writeMsg(stderr, source);1044 const w = &stderr.file_writer.interface;
1034 try stderr.writeByte('\n');1045 try diagnostics.writeMsg(w, source);
1046 try w.writeByte('\n');
1035 return err;1047 return err;
1036 },1048 },
1037 };1049 };
src/libs/netbsd.zig+13-13
...@@ -1,9 +1,9 @@...@@ -1,9 +1,9 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
3const mem = std.mem;4const mem = std.mem;
4const log = std.log;5const log = std.log;
5const fs = std.fs;6const path = std.Io.Dir.path;
6const path = fs.path;
7const assert = std.debug.assert;7const assert = std.debug.assert;
8const Version = std.SemanticVersion;8const Version = std.SemanticVersion;
9const Path = std.Build.Cache.Path;9const Path = std.Build.Cache.Path;
...@@ -346,8 +346,8 @@ pub const BuiltSharedObjects = struct {...@@ -346,8 +346,8 @@ pub const BuiltSharedObjects = struct {
346 lock: Cache.Lock,346 lock: Cache.Lock,
347 dir_path: Path,347 dir_path: Path,
348348
349 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void {349 pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator, io: Io) void {
350 self.lock.release();350 self.lock.release(io);
351 gpa.free(self.dir_path.sub_path);351 gpa.free(self.dir_path.sub_path);
352 self.* = undefined;352 self.* = undefined;
353 }353 }
...@@ -385,12 +385,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -385,12 +385,12 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
385 var cache: Cache = .{385 var cache: Cache = .{
386 .gpa = gpa,386 .gpa = gpa,
387 .io = io,387 .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", .{}),
389 };389 };
390 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });390 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
391 cache.addPrefix(comp.dirs.zig_lib);391 cache.addPrefix(comp.dirs.zig_lib);
392 cache.addPrefix(comp.dirs.global_cache);392 cache.addPrefix(comp.dirs.global_cache);
393 defer cache.manifest_dir.close();393 defer cache.manifest_dir.close(io);
394394
395 var man = cache.obtain();395 var man = cache.obtain();
396 defer man.deinit();396 defer man.deinit();
...@@ -409,7 +409,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -409,7 +409,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
409 .lock = man.toOwnedLock(),409 .lock = man.toOwnedLock(),
410 .dir_path = .{410 .dir_path = .{
411 .root_dir = comp.dirs.global_cache,411 .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),
413 },413 },
414 });414 });
415 }415 }
...@@ -418,10 +418,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -418,10 +418,10 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
418 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });418 const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
419419
420 var o_directory: Cache.Directory = .{420 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, .{}),
422 .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),422 .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),
423 };423 };
424 defer o_directory.handle.close();424 defer o_directory.handle.close(io);
425425
426 const abilists_contents = man.files.keys()[abilists_index].contents.?;426 const abilists_contents = man.files.keys()[abilists_index].contents.?;
427 const metadata = try loadMetaData(gpa, abilists_contents);427 const metadata = try loadMetaData(gpa, abilists_contents);
...@@ -628,7 +628,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -628,7 +628,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
628628
629 var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc.629 var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", etc.
630 const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;630 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 });
632 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);632 try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
633 }633 }
634634
...@@ -640,7 +640,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -640,7 +640,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
640 .lock = man.toOwnedLock(),640 .lock = man.toOwnedLock(),
641 .dir_path = .{641 .dir_path = .{
642 .root_dir = comp.dirs.global_cache,642 .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),
644 },644 },
645 });645 });
646}646}
...@@ -661,7 +661,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C...@@ -661,7 +661,7 @@ fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.C
661 const so_path: Path = .{661 const so_path: Path = .{
662 .root_dir = so_files.dir_path.root_dir,662 .root_dir = so_files.dir_path.root_dir,
663 .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{663 .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,
665 }) catch return comp.setAllocFailure(),665 }) catch return comp.setAllocFailure(),
666 };666 };
667 task_buffer[task_buffer_i] = .{ .load_dso = so_path };667 task_buffer[task_buffer_i] = .{ .load_dso = so_path };
src/link.zig+138-86
...@@ -393,7 +393,7 @@ pub const File = struct {...@@ -393,7 +393,7 @@ pub const File = struct {
393 comp: *Compilation,393 comp: *Compilation,
394 emit: Path,394 emit: Path,
395395
396 file: ?fs.File,396 file: ?Io.File,
397 /// When using the LLVM backend, the emitted object is written to a file with this name. This397 /// When using the LLVM backend, the emitted object is written to a file with this name. This
398 /// object file then becomes a normal link input to LLD or a self-hosted linker.398 /// object file then becomes a normal link input to LLD or a self-hosted linker.
399 ///399 ///
...@@ -620,16 +620,16 @@ pub const File = struct {...@@ -620,16 +620,16 @@ pub const File = struct {
620 emit.sub_path, std.crypto.random.int(u32),620 emit.sub_path, std.crypto.random.int(u32),
621 });621 });
622 defer gpa.free(tmp_sub_path);622 defer gpa.free(tmp_sub_path);
623 try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_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.sub_path);624 try emit.root_dir.handle.rename(tmp_sub_path, emit.root_dir.handle, emit.sub_path, io);
625 switch (builtin.os.tag) {625 switch (builtin.os.tag) {
626 .linux => std.posix.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| {626 .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});
628 },628 },
629 .maccatalyst, .macos => {629 .maccatalyst, .macos => {
630 const macho_file = base.cast(.macho).?;630 const macho_file = base.cast(.macho).?;
631 macho_file.ptraceAttach(pid) catch |err| {631 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});
633 };633 };
634 },634 },
635 .windows => unreachable,635 .windows => unreachable,
...@@ -637,7 +637,7 @@ pub const File = struct {...@@ -637,7 +637,7 @@ pub const File = struct {
637 }637 }
638 }638 }
639 }639 }
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 });
641 },641 },
642 .elf2, .coff2 => if (base.file == null) {642 .elf2, .coff2 => if (base.file == null) {
643 const mf = if (base.cast(.elf2)) |elf|643 const mf = if (base.cast(.elf2)) |elf|
...@@ -646,10 +646,10 @@ pub const File = struct {...@@ -646,10 +646,10 @@ pub const File = struct {
646 &coff.mf646 &coff.mf
647 else647 else
648 unreachable;648 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, .{
650 .mode = .read_write,650 .mode = .read_write,
651 });651 });
652 base.file = .adaptFromNewApi(mf.file);652 base.file = mf.file;
653 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));653 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));
654 },654 },
655 .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }),655 .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }),
...@@ -687,7 +687,7 @@ pub const File = struct {...@@ -687,7 +687,7 @@ pub const File = struct {
687 .lld => assert(base.file == null),687 .lld => assert(base.file == null),
688 .elf => if (base.file) |f| {688 .elf => if (base.file) |f| {
689 dev.check(.elf_linker);689 dev.check(.elf_linker);
690 f.close();690 f.close(io);
691 base.file = null;691 base.file = null;
692692
693 if (base.child_pid) |pid| {693 if (base.child_pid) |pid| {
...@@ -701,7 +701,7 @@ pub const File = struct {...@@ -701,7 +701,7 @@ pub const File = struct {
701 },701 },
702 .macho, .wasm => if (base.file) |f| {702 .macho, .wasm => if (base.file) |f| {
703 dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker });703 dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker });
704 f.close();704 f.close(io);
705 base.file = null;705 base.file = null;
706706
707 if (base.child_pid) |pid| {707 if (base.child_pid) |pid| {
...@@ -852,10 +852,12 @@ pub const File = struct {...@@ -852,10 +852,12 @@ pub const File = struct {
852 }852 }
853 }853 }
854854
855 pub fn releaseLock(self: *File) void {855 pub fn releaseLock(base: *File) void {
856 if (self.lock) |*lock| {856 const comp = base.comp;
857 lock.release();857 const io = comp.io;
858 self.lock = null;858 if (base.lock) |*lock| {
859 lock.release(io);
860 base.lock = null;
859 }861 }
860 }862 }
861863
...@@ -866,8 +868,9 @@ pub const File = struct {...@@ -866,8 +868,9 @@ pub const File = struct {
866 }868 }
867869
868 pub fn destroy(base: *File) void {870 pub fn destroy(base: *File) void {
871 const io = base.comp.io;
869 base.releaseLock();872 base.releaseLock();
870 if (base.file) |f| f.close();873 if (base.file) |f| f.close(io);
871 switch (base.tag) {874 switch (base.tag) {
872 .plan9 => unreachable,875 .plan9 => unreachable,
873 inline else => |tag| {876 inline else => |tag| {
...@@ -897,16 +900,16 @@ pub const File = struct {...@@ -897,16 +900,16 @@ pub const File = struct {
897 }900 }
898 }901 }
899902
900 pub const FlushError = error{903 pub const FlushError = Io.Cancelable || Allocator.Error || error{
901 /// Indicates an error will be present in `Compilation.link_diags`.904 /// Indicates an error will be present in `Compilation.link_diags`.
902 LinkFailure,905 LinkFailure,
903 OutOfMemory,
904 };906 };
905907
906 /// Commit pending changes and write headers. Takes into account final output mode.908 /// Commit pending changes and write headers. Takes into account final output mode.
907 /// `arena` has the lifetime of the call to `Compilation.update`.909 /// `arena` has the lifetime of the call to `Compilation.update`.
908 pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void {910 pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void {
909 const comp = base.comp;911 const comp = base.comp;
912 const io = comp.io;
910 if (comp.clang_preprocessor_mode == .yes or comp.clang_preprocessor_mode == .pch) {913 if (comp.clang_preprocessor_mode == .yes or comp.clang_preprocessor_mode == .pch) {
911 dev.check(.clang_command);914 dev.check(.clang_command);
912 const emit = base.emit;915 const emit = base.emit;
...@@ -917,12 +920,19 @@ pub const File = struct {...@@ -917,12 +920,19 @@ pub const File = struct {
917 assert(comp.c_object_table.count() == 1);920 assert(comp.c_object_table.count() == 1);
918 const the_key = comp.c_object_table.keys()[0];921 const the_key = comp.c_object_table.keys()[0];
919 const cached_pp_file_path = the_key.status.success.object_path;922 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| {
921 const diags = &base.comp.link_diags;931 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}", .{
923 std.fmt.alt(@as(Path, cached_pp_file_path), .formatEscapeChar),933 std.fmt.alt(@as(Path, cached_pp_file_path), .formatEscapeChar),
924 std.fmt.alt(@as(Path, emit), .formatEscapeChar),934 std.fmt.alt(@as(Path, emit), .formatEscapeChar),
925 @errorName(err),935 err,
926 });936 });
927 };937 };
928 return;938 return;
...@@ -1060,9 +1070,10 @@ pub const File = struct {...@@ -1060,9 +1070,10 @@ pub const File = struct {
1060 /// Opens a path as an object file and parses it into the linker.1070 /// Opens a path as an object file and parses it into the linker.
1061 fn openLoadObject(base: *File, path: Path) anyerror!void {1071 fn openLoadObject(base: *File, path: Path) anyerror!void {
1062 if (base.tag == .lld) return;1072 if (base.tag == .lld) return;
1073 const io = base.comp.io;
1063 const diags = &base.comp.link_diags;1074 const diags = &base.comp.link_diags;
1064 const input = try openObjectInput(diags, path);1075 const input = try openObjectInput(io, diags, path);
1065 errdefer input.object.file.close();1076 errdefer input.object.file.close(io);
1066 try loadInput(base, input);1077 try loadInput(base, input);
1067 }1078 }
10681079
...@@ -1070,21 +1081,22 @@ pub const File = struct {...@@ -1070,21 +1081,22 @@ pub const File = struct {
1070 /// If `query` is non-null, allows GNU ld scripts.1081 /// If `query` is non-null, allows GNU ld scripts.
1071 fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void {1082 fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void {
1072 if (base.tag == .lld) return;1083 if (base.tag == .lld) return;
1084 const io = base.comp.io;
1073 if (opt_query) |query| {1085 if (opt_query) |query| {
1074 const archive = try openObject(path, query.must_link, query.hidden);1086 const archive = try openObject(io, path, query.must_link, query.hidden);
1075 errdefer archive.file.close();1087 errdefer archive.file.close(io);
1076 loadInput(base, .{ .archive = archive }) catch |err| switch (err) {1088 loadInput(base, .{ .archive = archive }) catch |err| switch (err) {
1077 error.BadMagic, error.UnexpectedEndOfFile => {1089 error.BadMagic, error.UnexpectedEndOfFile => {
1078 if (base.tag != .elf and base.tag != .elf2) return err;1090 if (base.tag != .elf and base.tag != .elf2) return err;
1079 try loadGnuLdScript(base, path, query, archive.file);1091 try loadGnuLdScript(base, path, query, archive.file);
1080 archive.file.close();1092 archive.file.close(io);
1081 return;1093 return;
1082 },1094 },
1083 else => return err,1095 else => return err,
1084 };1096 };
1085 } else {1097 } else {
1086 const archive = try openObject(path, false, false);1098 const archive = try openObject(io, path, false, false);
1087 errdefer archive.file.close();1099 errdefer archive.file.close(io);
1088 try loadInput(base, .{ .archive = archive });1100 try loadInput(base, .{ .archive = archive });
1089 }1101 }
1090 }1102 }
...@@ -1093,29 +1105,30 @@ pub const File = struct {...@@ -1093,29 +1105,30 @@ pub const File = struct {
1093 /// Handles GNU ld scripts.1105 /// Handles GNU ld scripts.
1094 fn openLoadDso(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void {1106 fn openLoadDso(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void {
1095 if (base.tag == .lld) return;1107 if (base.tag == .lld) return;
1096 const dso = try openDso(path, query.needed, query.weak, query.reexport);1108 const io = base.comp.io;
1097 errdefer dso.file.close();1109 const dso = try openDso(io, path, query.needed, query.weak, query.reexport);
1110 errdefer dso.file.close(io);
1098 loadInput(base, .{ .dso = dso }) catch |err| switch (err) {1111 loadInput(base, .{ .dso = dso }) catch |err| switch (err) {
1099 error.BadMagic, error.UnexpectedEndOfFile => {1112 error.BadMagic, error.UnexpectedEndOfFile => {
1100 if (base.tag != .elf and base.tag != .elf2) return err;1113 if (base.tag != .elf and base.tag != .elf2) return err;
1101 try loadGnuLdScript(base, path, query, dso.file);1114 try loadGnuLdScript(base, path, query, dso.file);
1102 dso.file.close();1115 dso.file.close(io);
1103 return;1116 return;
1104 },1117 },
1105 else => return err,1118 else => return err,
1106 };1119 };
1107 }1120 }
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 {
1110 const comp = base.comp;1123 const comp = base.comp;
1124 const io = comp.io;
1111 const diags = &comp.link_diags;1125 const diags = &comp.link_diags;
1112 const gpa = comp.gpa;1126 const gpa = comp.gpa;
1113 const io = comp.io;1127 const stat = try file.stat(io);
1114 const stat = try file.stat();
1115 const size = std.math.cast(u32, stat.size) orelse return error.FileTooBig;1128 const size = std.math.cast(u32, stat.size) orelse return error.FileTooBig;
1116 const buf = try gpa.alloc(u8, size);1129 const buf = try gpa.alloc(u8, size);
1117 defer gpa.free(buf);1130 defer gpa.free(buf);
1118 const n = try file.preadAll(buf, 0);1131 const n = try file.readPositionalAll(io, buf, 0);
1119 if (buf.len != n) return error.UnexpectedEndOfFile;1132 if (buf.len != n) return error.UnexpectedEndOfFile;
1120 var ld_script = try LdScript.parse(gpa, diags, path, buf);1133 var ld_script = try LdScript.parse(gpa, diags, path, buf);
1121 defer ld_script.deinit(gpa);1134 defer ld_script.deinit(gpa);
...@@ -1180,6 +1193,32 @@ pub const File = struct {...@@ -1180,6 +1193,32 @@ pub const File = struct {
1180 }1193 }
1181 }1194 }
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
1183 pub const Tag = enum {1222 pub const Tag = enum {
1184 coff2,1223 coff2,
1185 elf,1224 elf,
...@@ -1231,22 +1270,26 @@ pub const File = struct {...@@ -1231,22 +1270,26 @@ pub const File = struct {
1231 ty: InternPool.Index,1270 ty: InternPool.Index,
1232 };1271 };
12331272
1234 pub fn determineMode(1273 pub fn determinePermissions(
1235 output_mode: std.builtin.OutputMode,1274 output_mode: std.builtin.OutputMode,
1236 link_mode: std.builtin.LinkMode,1275 link_mode: std.builtin.LinkMode,
1237 ) fs.File.Mode {1276 ) Io.File.Permissions {
1238 // On common systems with a 0o022 umask, 0o777 will still result in a file created1277 // On common systems with a 0o022 umask, 0o777 will still result in a file created
1239 // with 0o755 permissions, but it works appropriately if the system is configured1278 // with 0o755 permissions, but it works appropriately if the system is configured
1240 // more leniently. As another data point, C's fopen seems to open files with the1279 // more leniently. As another data point, C's fopen seems to open files with the
1241 // 666 mode.1280 // 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
1243 switch (output_mode) {1286 switch (output_mode) {
1244 .Lib => return switch (link_mode) {1287 .Lib => return switch (link_mode) {
1245 .dynamic => executable_mode,1288 .dynamic => executable_mode,
1246 .static => fs.File.default_mode,1289 .static => .default_file,
1247 },1290 },
1248 .Exe => return executable_mode,1291 .Exe => return executable_mode,
1249 .Obj => return fs.File.default_mode,1292 .Obj => return .default_file,
1250 }1293 }
1251 }1294 }
12521295
...@@ -1656,19 +1699,19 @@ pub const Input = union(enum) {...@@ -1656,19 +1699,19 @@ pub const Input = union(enum) {
16561699
1657 pub const Object = struct {1700 pub const Object = struct {
1658 path: Path,1701 path: Path,
1659 file: fs.File,1702 file: Io.File,
1660 must_link: bool,1703 must_link: bool,
1661 hidden: bool,1704 hidden: bool,
1662 };1705 };
16631706
1664 pub const Res = struct {1707 pub const Res = struct {
1665 path: Path,1708 path: Path,
1666 file: fs.File,1709 file: Io.File,
1667 };1710 };
16681711
1669 pub const Dso = struct {1712 pub const Dso = struct {
1670 path: Path,1713 path: Path,
1671 file: fs.File,1714 file: Io.File,
1672 needed: bool,1715 needed: bool,
1673 weak: bool,1716 weak: bool,
1674 reexport: bool,1717 reexport: bool,
...@@ -1690,7 +1733,7 @@ pub const Input = union(enum) {...@@ -1690,7 +1733,7 @@ pub const Input = union(enum) {
1690 }1733 }
16911734
1692 /// Returns `null` in the case of `dso_exact`.1735 /// 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 } {
1694 return switch (input) {1737 return switch (input) {
1695 .object, .archive => |obj| .{ obj.path, obj.file },1738 .object, .archive => |obj| .{ obj.path, obj.file },
1696 inline .res, .dso => |x| .{ x.path, x.file },1739 inline .res, .dso => |x| .{ x.path, x.file },
...@@ -1735,6 +1778,7 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {...@@ -1735,6 +1778,7 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {
1735pub fn resolveInputs(1778pub fn resolveInputs(
1736 gpa: Allocator,1779 gpa: Allocator,
1737 arena: Allocator,1780 arena: Allocator,
1781 io: Io,
1738 target: *const std.Target,1782 target: *const std.Target,
1739 /// This function mutates this array but does not take ownership.1783 /// This function mutates this array but does not take ownership.
1740 /// Allocated with `gpa`.1784 /// Allocated with `gpa`.
...@@ -1784,6 +1828,7 @@ pub fn resolveInputs(...@@ -1784,6 +1828,7 @@ pub fn resolveInputs(
1784 for (lib_directories) |lib_directory| switch (try resolveLibInput(1828 for (lib_directories) |lib_directory| switch (try resolveLibInput(
1785 gpa,1829 gpa,
1786 arena,1830 arena,
1831 io,
1787 unresolved_inputs,1832 unresolved_inputs,
1788 resolved_inputs,1833 resolved_inputs,
1789 &checked_paths,1834 &checked_paths,
...@@ -1810,6 +1855,7 @@ pub fn resolveInputs(...@@ -1810,6 +1855,7 @@ pub fn resolveInputs(
1810 for (lib_directories) |lib_directory| switch (try resolveLibInput(1855 for (lib_directories) |lib_directory| switch (try resolveLibInput(
1811 gpa,1856 gpa,
1812 arena,1857 arena,
1858 io,
1813 unresolved_inputs,1859 unresolved_inputs,
1814 resolved_inputs,1860 resolved_inputs,
1815 &checked_paths,1861 &checked_paths,
...@@ -1837,6 +1883,7 @@ pub fn resolveInputs(...@@ -1837,6 +1883,7 @@ pub fn resolveInputs(
1837 switch (try resolveLibInput(1883 switch (try resolveLibInput(
1838 gpa,1884 gpa,
1839 arena,1885 arena,
1886 io,
1840 unresolved_inputs,1887 unresolved_inputs,
1841 resolved_inputs,1888 resolved_inputs,
1842 &checked_paths,1889 &checked_paths,
...@@ -1855,6 +1902,7 @@ pub fn resolveInputs(...@@ -1855,6 +1902,7 @@ pub fn resolveInputs(
1855 switch (try resolveLibInput(1902 switch (try resolveLibInput(
1856 gpa,1903 gpa,
1857 arena,1904 arena,
1905 io,
1858 unresolved_inputs,1906 unresolved_inputs,
1859 resolved_inputs,1907 resolved_inputs,
1860 &checked_paths,1908 &checked_paths,
...@@ -1886,6 +1934,7 @@ pub fn resolveInputs(...@@ -1886,6 +1934,7 @@ pub fn resolveInputs(
1886 if (try resolvePathInput(1934 if (try resolvePathInput(
1887 gpa,1935 gpa,
1888 arena,1936 arena,
1937 io,
1889 unresolved_inputs,1938 unresolved_inputs,
1890 resolved_inputs,1939 resolved_inputs,
1891 &ld_script_bytes,1940 &ld_script_bytes,
...@@ -1903,6 +1952,7 @@ pub fn resolveInputs(...@@ -1903,6 +1952,7 @@ pub fn resolveInputs(
1903 switch ((try resolvePathInput(1952 switch ((try resolvePathInput(
1904 gpa,1953 gpa,
1905 arena,1954 arena,
1955 io,
1906 unresolved_inputs,1956 unresolved_inputs,
1907 resolved_inputs,1957 resolved_inputs,
1908 &ld_script_bytes,1958 &ld_script_bytes,
...@@ -1930,6 +1980,7 @@ pub fn resolveInputs(...@@ -1930,6 +1980,7 @@ pub fn resolveInputs(
1930 if (try resolvePathInput(1980 if (try resolvePathInput(
1931 gpa,1981 gpa,
1932 arena,1982 arena,
1983 io,
1933 unresolved_inputs,1984 unresolved_inputs,
1934 resolved_inputs,1985 resolved_inputs,
1935 &ld_script_bytes,1986 &ld_script_bytes,
...@@ -1969,6 +2020,7 @@ const fatal = std.process.fatal;...@@ -1969,6 +2020,7 @@ const fatal = std.process.fatal;
1969fn resolveLibInput(2020fn resolveLibInput(
1970 gpa: Allocator,2021 gpa: Allocator,
1971 arena: Allocator,2022 arena: Allocator,
2023 io: Io,
1972 /// Allocated via `gpa`.2024 /// Allocated via `gpa`.
1973 unresolved_inputs: *std.ArrayList(UnresolvedInput),2025 unresolved_inputs: *std.ArrayList(UnresolvedInput),
1974 /// Allocated via `gpa`.2026 /// Allocated via `gpa`.
...@@ -1994,11 +2046,11 @@ fn resolveLibInput(...@@ -1994,11 +2046,11 @@ fn resolveLibInput(
1994 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.tbd", .{lib_name}),2046 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.tbd", .{lib_name}),
1995 };2047 };
1996 try checked_paths.print(gpa, "\n {f}", .{test_path});2048 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) {
1998 error.FileNotFound => break :tbd,2050 error.FileNotFound => break :tbd,
1999 else => |e| fatal("unable to search for tbd library '{f}': {s}", .{ test_path, @errorName(e) }),2051 else => |e| fatal("unable to search for tbd library '{f}': {s}", .{ test_path, @errorName(e) }),
2000 };2052 };
2001 errdefer file.close();2053 errdefer file.close(io);
2002 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query);2054 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query);
2003 }2055 }
20042056
...@@ -2013,7 +2065,7 @@ fn resolveLibInput(...@@ -2013,7 +2065,7 @@ fn resolveLibInput(
2013 }),2065 }),
2014 };2066 };
2015 try checked_paths.print(gpa, "\n {f}", .{test_path});2067 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, .{
2017 .path = test_path,2069 .path = test_path,
2018 .query = name_query.query,2070 .query = name_query.query,
2019 }, link_mode, color)) {2071 }, link_mode, color)) {
...@@ -2030,13 +2082,13 @@ fn resolveLibInput(...@@ -2030,13 +2082,13 @@ fn resolveLibInput(
2030 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}),2082 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}),
2031 };2083 };
2032 try checked_paths.print(gpa, "\n {f}", .{test_path});2084 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) {
2034 error.FileNotFound => break :so,2086 error.FileNotFound => break :so,
2035 else => |e| fatal("unable to search for so library '{f}': {s}", .{2087 else => |e| fatal("unable to search for so library '{f}': {s}", .{
2036 test_path, @errorName(e),2088 test_path, @errorName(e),
2037 }),2089 }),
2038 };2090 };
2039 errdefer file.close();2091 errdefer file.close(io);
2040 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query);2092 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query);
2041 }2093 }
20422094
...@@ -2048,11 +2100,11 @@ fn resolveLibInput(...@@ -2048,11 +2100,11 @@ fn resolveLibInput(
2048 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}),2100 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}),
2049 };2101 };
2050 try checked_paths.print(gpa, "\n {f}", .{test_path});2102 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) {
2052 error.FileNotFound => break :mingw,2104 error.FileNotFound => break :mingw,
2053 else => |e| fatal("unable to search for static library '{f}': {s}", .{ test_path, @errorName(e) }),2105 else => |e| fatal("unable to search for static library '{f}': {s}", .{ test_path, @errorName(e) }),
2054 };2106 };
2055 errdefer file.close();2107 errdefer file.close(io);
2056 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query);2108 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query);
2057 }2109 }
20582110
...@@ -2062,7 +2114,7 @@ fn resolveLibInput(...@@ -2062,7 +2114,7 @@ fn resolveLibInput(
2062fn finishResolveLibInput(2114fn finishResolveLibInput(
2063 resolved_inputs: *std.ArrayList(Input),2115 resolved_inputs: *std.ArrayList(Input),
2064 path: Path,2116 path: Path,
2065 file: std.fs.File,2117 file: Io.File,
2066 link_mode: std.builtin.LinkMode,2118 link_mode: std.builtin.LinkMode,
2067 query: UnresolvedInput.Query,2119 query: UnresolvedInput.Query,
2068) ResolveLibInputResult {2120) ResolveLibInputResult {
...@@ -2087,6 +2139,7 @@ fn finishResolveLibInput(...@@ -2087,6 +2139,7 @@ fn finishResolveLibInput(
2087fn resolvePathInput(2139fn resolvePathInput(
2088 gpa: Allocator,2140 gpa: Allocator,
2089 arena: Allocator,2141 arena: Allocator,
2142 io: Io,
2090 /// Allocated with `gpa`.2143 /// Allocated with `gpa`.
2091 unresolved_inputs: *std.ArrayList(UnresolvedInput),2144 unresolved_inputs: *std.ArrayList(UnresolvedInput),
2092 /// Allocated with `gpa`.2145 /// Allocated with `gpa`.
...@@ -2098,12 +2151,12 @@ fn resolvePathInput(...@@ -2098,12 +2151,12 @@ fn resolvePathInput(
2098 color: std.zig.Color,2151 color: std.zig.Color,
2099) Allocator.Error!?ResolveLibInputResult {2152) Allocator.Error!?ResolveLibInputResult {
2100 switch (Compilation.classifyFileExt(pq.path.sub_path)) {2153 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),2154 .static_library => return try resolvePathInputLib(gpa, arena, io, 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),2155 .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color),
2103 .object => {2156 .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|
2105 fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) });2158 fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) });
2106 errdefer file.close();2159 errdefer file.close(io);
2107 try resolved_inputs.append(gpa, .{ .object = .{2160 try resolved_inputs.append(gpa, .{ .object = .{
2108 .path = pq.path,2161 .path = pq.path,
2109 .file = file,2162 .file = file,
...@@ -2113,9 +2166,9 @@ fn resolvePathInput(...@@ -2113,9 +2166,9 @@ fn resolvePathInput(
2113 return null;2166 return null;
2114 },2167 },
2115 .res => {2168 .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|
2117 fatal("failed to open windows resource {f}: {s}", .{ pq.path, @errorName(err) });2170 fatal("failed to open windows resource {f}: {s}", .{ pq.path, @errorName(err) });
2118 errdefer file.close();2171 errdefer file.close(io);
2119 try resolved_inputs.append(gpa, .{ .res = .{2172 try resolved_inputs.append(gpa, .{ .res = .{
2120 .path = pq.path,2173 .path = pq.path,
2121 .file = file,2174 .file = file,
...@@ -2129,6 +2182,7 @@ fn resolvePathInput(...@@ -2129,6 +2182,7 @@ fn resolvePathInput(
2129fn resolvePathInputLib(2182fn resolvePathInputLib(
2130 gpa: Allocator,2183 gpa: Allocator,
2131 arena: Allocator,2184 arena: Allocator,
2185 io: Io,
2132 /// Allocated with `gpa`.2186 /// Allocated with `gpa`.
2133 unresolved_inputs: *std.ArrayList(UnresolvedInput),2187 unresolved_inputs: *std.ArrayList(UnresolvedInput),
2134 /// Allocated with `gpa`.2188 /// Allocated with `gpa`.
...@@ -2149,30 +2203,29 @@ fn resolvePathInputLib(...@@ -2149,30 +2203,29 @@ fn resolvePathInputLib(
2149 .static_library, .shared_library => true,2203 .static_library, .shared_library => true,
2150 else => false,2204 else => false,
2151 }) {2205 }) {
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) {
2153 error.FileNotFound => return .no_match,2207 error.FileNotFound => return .no_match,
2154 else => |e| fatal("unable to search for {s} library '{f}': {s}", .{2208 else => |e| fatal("unable to search for {t} library '{f}': {t}", .{
2155 @tagName(link_mode), std.fmt.alt(test_path, .formatEscapeChar), @errorName(e),2209 link_mode, std.fmt.alt(test_path, .formatEscapeChar), e,
2156 }),2210 }),
2157 };2211 };
2158 errdefer file.close();2212 errdefer file.close(io);
2159 try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len));2213 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}", .{2214 const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err|
2161 std.fmt.alt(test_path, .formatEscapeChar), @errorName(err),2215 fatal("failed to read '{f}': {t}", .{ std.fmt.alt(test_path, .formatEscapeChar), err });
2162 });
2163 const buf = ld_script_bytes.items[0..n];2216 const buf = ld_script_bytes.items[0..n];
2164 if (mem.startsWith(u8, buf, std.elf.MAGIC) or mem.startsWith(u8, buf, std.elf.ARMAG)) {2217 if (mem.startsWith(u8, buf, std.elf.MAGIC) or mem.startsWith(u8, buf, std.elf.ARMAG)) {
2165 // Appears to be an ELF or archive file.2218 // Appears to be an ELF or archive file.
2166 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query);2219 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query);
2167 }2220 }
2168 const stat = file.stat() catch |err|2221 const stat = file.stat(io) catch |err|
2169 fatal("failed to stat {f}: {s}", .{ test_path, @errorName(err) });2222 fatal("failed to stat {f}: {t}", .{ test_path, err });
2170 const size = std.math.cast(u32, stat.size) orelse2223 const size = std.math.cast(u32, stat.size) orelse
2171 fatal("{f}: linker script too big", .{test_path});2224 fatal("{f}: linker script too big", .{test_path});
2172 try ld_script_bytes.resize(gpa, size);2225 try ld_script_bytes.resize(gpa, size);
2173 const buf2 = ld_script_bytes.items[n..];2226 const buf2 = ld_script_bytes.items[n..];
2174 const n2 = file.preadAll(buf2, n) catch |err|2227 const n2 = file.readPositionalAll(io, buf2, n) catch |err|
2175 fatal("failed to read {f}: {s}", .{ test_path, @errorName(err) });2228 fatal("failed to read {f}: {t}", .{ test_path, err });
2176 if (n2 != buf2.len) fatal("failed to read {f}: unexpected end of file", .{test_path});2229 if (n2 != buf2.len) fatal("failed to read {f}: unexpected end of file", .{test_path});
21772230
2178 // This `Io` is only used for a mutex, and we know we aren't doing anything async/concurrent.2231 // This `Io` is only used for a mutex, and we know we aren't doing anything async/concurrent.
...@@ -2192,13 +2245,12 @@ fn resolvePathInputLib(...@@ -2192,13 +2245,12 @@ fn resolvePathInputLib(
2192 var error_bundle = try wip_errors.toOwnedBundle("");2245 var error_bundle = try wip_errors.toOwnedBundle("");
2193 defer error_bundle.deinit(gpa);2246 defer error_bundle.deinit(gpa);
21942247
2195 error_bundle.renderToStdErr(.{}, color);2248 error_bundle.renderToStderr(io, .{}, color) catch {};
2196
2197 std.process.exit(1);2249 std.process.exit(1);
2198 }2250 }
21992251
2200 var ld_script = ld_script_result catch |err|2252 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 });
2202 defer ld_script.deinit(gpa);2254 defer ld_script.deinit(gpa);
22032255
2204 try unresolved_inputs.ensureUnusedCapacity(gpa, ld_script.args.len);2256 try unresolved_inputs.ensureUnusedCapacity(gpa, ld_script.args.len);
...@@ -2223,23 +2275,23 @@ fn resolvePathInputLib(...@@ -2223,23 +2275,23 @@ fn resolvePathInputLib(
2223 } });2275 } });
2224 }2276 }
2225 }2277 }
2226 file.close();2278 file.close(io);
2227 return .ok;2279 return .ok;
2228 }2280 }
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) {
2231 error.FileNotFound => return .no_match,2283 error.FileNotFound => return .no_match,
2232 else => |e| fatal("unable to search for {s} library {f}: {s}", .{2284 else => |e| fatal("unable to search for {s} library {f}: {s}", .{
2233 @tagName(link_mode), test_path, @errorName(e),2285 @tagName(link_mode), test_path, @errorName(e),
2234 }),2286 }),
2235 };2287 };
2236 errdefer file.close();2288 errdefer file.close(io);
2237 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query);2289 return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query);
2238}2290}
22392291
2240pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object {2292pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object {
2241 var file = try path.root_dir.handle.openFile(path.sub_path, .{});2293 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
2242 errdefer file.close();2294 errdefer file.close(io);
2243 return .{2295 return .{
2244 .path = path,2296 .path = path,
2245 .file = file,2297 .file = file,
...@@ -2248,9 +2300,9 @@ pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object {...@@ -2248,9 +2300,9 @@ pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object {
2248 };2300 };
2249}2301}
22502302
2251pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso {2303pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso {
2252 var file = try path.root_dir.handle.openFile(path.sub_path, .{});2304 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
2253 errdefer file.close();2305 errdefer file.close(io);
2254 return .{2306 return .{
2255 .path = path,2307 .path = path,
2256 .file = file,2308 .file = file,
...@@ -2260,20 +2312,20 @@ pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso...@@ -2260,20 +2312,20 @@ pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso
2260 };2312 };
2261}2313}
22622314
2263pub fn openObjectInput(diags: *Diags, path: Path) error{LinkFailure}!Input {2315pub fn openObjectInput(io: Io, diags: *Diags, path: Path) error{LinkFailure}!Input {
2264 return .{ .object = openObject(path, false, false) catch |err| {2316 return .{ .object = openObject(io, path, false, false) catch |err| {
2265 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });2317 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });
2266 } };2318 } };
2267}2319}
22682320
2269pub fn openArchiveInput(diags: *Diags, path: Path, must_link: bool, hidden: bool) error{LinkFailure}!Input {2321pub fn openArchiveInput(io: Io, diags: *Diags, path: Path, must_link: bool, hidden: bool) error{LinkFailure}!Input {
2270 return .{ .archive = openObject(path, must_link, hidden) catch |err| {2322 return .{ .archive = openObject(io, path, must_link, hidden) catch |err| {
2271 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });2323 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });
2272 } };2324 } };
2273}2325}
22742326
2275pub fn openDsoInput(diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{LinkFailure}!Input {2327pub fn openDsoInput(io: Io, diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{LinkFailure}!Input {
2276 return .{ .dso = openDso(path, needed, weak, reexport) catch |err| {2328 return .{ .dso = openDso(io, path, needed, weak, reexport) catch |err| {
2277 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });2329 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });
2278 } };2330 } };
2279}2331}
src/link/C.zig+10-7
...@@ -124,6 +124,7 @@ pub fn createEmpty(...@@ -124,6 +124,7 @@ pub fn createEmpty(
124 emit: Path,124 emit: Path,
125 options: link.File.OpenOptions,125 options: link.File.OpenOptions,
126) !*C {126) !*C {
127 const io = comp.io;
127 const target = &comp.root_mod.resolved_target.result;128 const target = &comp.root_mod.resolved_target.result;
128 assert(target.ofmt == .c);129 assert(target.ofmt == .c);
129 const optimize_mode = comp.root_mod.optimize_mode;130 const optimize_mode = comp.root_mod.optimize_mode;
...@@ -135,11 +136,11 @@ pub fn createEmpty(...@@ -135,11 +136,11 @@ pub fn createEmpty(
135 assert(!use_lld);136 assert(!use_lld);
136 assert(!use_llvm);137 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, .{
139 // Truncation is done on `flush`.140 // Truncation is done on `flush`.
140 .truncate = false,141 .truncate = false,
141 });142 });
142 errdefer file.close();143 errdefer file.close(io);
143144
144 const c_file = try arena.create(C);145 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...@@ -370,6 +371,7 @@ pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.P
370 const comp = self.base.comp;371 const comp = self.base.comp;
371 const diags = &comp.link_diags;372 const diags = &comp.link_diags;
372 const gpa = comp.gpa;373 const gpa = comp.gpa;
374 const io = comp.io;
373 const zcu = self.base.comp.zcu.?;375 const zcu = self.base.comp.zcu.?;
374 const ip = &zcu.intern_pool;376 const ip = &zcu.intern_pool;
375 const pt: Zcu.PerThread = .activate(zcu, tid);377 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...@@ -507,8 +509,8 @@ pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.P
507 }, self.getString(av_block.code));509 }, self.getString(av_block.code));
508510
509 const file = self.base.file.?;511 const file = self.base.file.?;
510 file.setEndPos(f.file_size) catch |err| return diags.fail("failed to allocate file: {s}", .{@errorName(err)});512 file.setLength(io, f.file_size) catch |err| return diags.fail("failed to allocate file: {t}", .{err});
511 var fw = file.writer(&.{});513 var fw = file.writer(io, &.{});
512 var w = &fw.interface;514 var w = &fw.interface;
513 w.writeVecAll(f.all_buffers.items) catch |err| switch (err) {515 w.writeVecAll(f.all_buffers.items) catch |err| switch (err) {
514 error.WriteFailed => return diags.fail("failed to write to '{f}': {s}", .{516 error.WriteFailed => return diags.fail("failed to write to '{f}': {s}", .{
...@@ -763,6 +765,7 @@ pub fn flushEmitH(zcu: *Zcu) !void {...@@ -763,6 +765,7 @@ pub fn flushEmitH(zcu: *Zcu) !void {
763 if (true) return; // emit-h is regressed765 if (true) return; // emit-h is regressed
764766
765 const emit_h = zcu.emit_h orelse return;767 const emit_h = zcu.emit_h orelse return;
768 const io = zcu.comp.io;
766769
767 // We collect a list of buffers to write, and write them all at once with pwritev 😎770 // We collect a list of buffers to write, and write them all at once with pwritev 😎
768 const num_buffers = emit_h.decl_table.count() + 1;771 const num_buffers = emit_h.decl_table.count() + 1;
...@@ -790,14 +793,14 @@ pub fn flushEmitH(zcu: *Zcu) !void {...@@ -790,14 +793,14 @@ pub fn flushEmitH(zcu: *Zcu) !void {
790 }793 }
791794
792 const directory = emit_h.loc.directory orelse zcu.comp.local_cache_directory;795 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, .{
794 // We set the end position explicitly below; by not truncating the file, we possibly797 // We set the end position explicitly below; by not truncating the file, we possibly
795 // make it easier on the file system by doing 1 reallocation instead of two.798 // make it easier on the file system by doing 1 reallocation instead of two.
796 .truncate = false,799 .truncate = false,
797 });800 });
798 defer file.close();801 defer file.close(io);
799802
800 try file.setEndPos(file_size);803 try file.setLength(io, file_size);
801 try file.pwritevAll(all_buffers.items, 0);804 try file.pwritevAll(all_buffers.items, 0);
802}805}
803806
src/link/Coff.zig+42-32
...@@ -1,3 +1,23 @@...@@ -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
1base: link.File,21base: link.File,
2mf: MappedFile,22mf: MappedFile,
3nodes: std.MultiArrayList(Node),23nodes: std.MultiArrayList(Node),
...@@ -631,12 +651,14 @@ fn create(...@@ -631,12 +651,14 @@ fn create(
631 else => return error.UnsupportedCOFFArchitecture,651 else => return error.UnsupportedCOFFArchitecture,
632 };652 };
633653
654 const io = comp.io;
655
634 const coff = try arena.create(Coff);656 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, .{
636 .read = true,658 .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),
638 });660 });
639 errdefer file.close(comp.io);661 errdefer file.close(io);
640 coff.* = .{662 coff.* = .{
641 .base = .{663 .base = .{
642 .tag = .coff2,664 .tag = .coff2,
...@@ -644,14 +666,14 @@ fn create(...@@ -644,14 +666,14 @@ fn create(
644 .comp = comp,666 .comp = comp,
645 .emit = path,667 .emit = path,
646668
647 .file = .adaptFromNewApi(file),669 .file = file,
648 .gc_sections = false,670 .gc_sections = false,
649 .print_gc_sections = false,671 .print_gc_sections = false,
650 .build_id = .none,672 .build_id = .none,
651 .allow_shlib_undefined = false,673 .allow_shlib_undefined = false,
652 .stack_size = 0,674 .stack_size = 0,
653 },675 },
654 .mf = try .init(file, comp.gpa),676 .mf = try .init(file, comp.gpa, io),
655 .nodes = .empty,677 .nodes = .empty,
656 .import_table = .{678 .import_table = .{
657 .ni = .none,679 .ni = .none,
...@@ -1727,22 +1749,20 @@ pub fn flush(...@@ -1727,22 +1749,20 @@ pub fn flush(
1727 const comp = coff.base.comp;1749 const comp = coff.base.comp;
1728 if (comp.compiler_rt_dyn_lib) |crt_file| {1750 if (comp.compiler_rt_dyn_lib) |crt_file| {
1729 const gpa = comp.gpa;1751 const gpa = comp.gpa;
1752 const io = comp.io;
1730 const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{1753 const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{
1731 std.fs.path.dirname(coff.base.emit.sub_path) orelse "",1754 std.fs.path.dirname(coff.base.emit.sub_path) orelse "",
1732 std.fs.path.basename(crt_file.full_object_path.sub_path),1755 std.fs.path.basename(crt_file.full_object_path.sub_path),
1733 });1756 });
1734 defer gpa.free(compiler_rt_sub_path);1757 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,
1736 crt_file.full_object_path.sub_path,1760 crt_file.full_object_path.sub_path,
1737 coff.base.emit.root_dir.handle,1761 coff.base.emit.root_dir.handle,
1738 compiler_rt_sub_path,1762 compiler_rt_sub_path,
1763 io,
1739 .{},1764 .{},
1740 ) catch |err| switch (err) {1765 ) catch |err| return comp.link_diags.fail("copy '{s}' failed: {t}", .{ compiler_rt_sub_path, err });
1741 else => |e| return comp.link_diags.fail("Copy '{s}' failed: {s}", .{
1742 compiler_rt_sub_path,
1743 @errorName(e),
1744 }),
1745 };
1746 }1766 }
1747}1767}
17481768
...@@ -2358,10 +2378,16 @@ pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTe...@@ -2358,10 +2378,16 @@ pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTe
2358 _ = name;2378 _ = name;
2359}2379}
23602380
2361pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) void {2381pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) Io.Cancelable!void {
2362 const w, _ = std.debug.lockStderrWriter(&.{});2382 const comp = coff.base.comp;
2363 defer std.debug.unlockStderrWriter();2383 const io = comp.io;
2364 coff.printNode(tid, w, .root, 0) catch {};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 };
2365}2391}
23662392
2367pub fn printNode(2393pub fn printNode(
...@@ -2459,19 +2485,3 @@ pub fn printNode(...@@ -2459,19 +2485,3 @@ pub fn printNode(
2459 }2485 }
2460 }2486 }
2461}2487}
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 @@...@@ -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
1gpa: Allocator,22gpa: Allocator,
2bin_file: *link.File,23bin_file: *link.File,
3format: DW.Format,24format: DW.Format,
...@@ -27,18 +48,18 @@ pub const UpdateError = error{...@@ -27,18 +48,18 @@ pub const UpdateError = error{
27 EndOfStream,48 EndOfStream,
28 Underflow,49 Underflow,
29 UnexpectedEndOfFile,50 UnexpectedEndOfFile,
51 NonResizable,
30} ||52} ||
31 codegen.GenerateSymbolError ||53 codegen.GenerateSymbolError ||
32 std.fs.File.OpenError ||54 Io.File.OpenError ||
33 std.fs.File.SetEndPosError ||55 Io.File.LengthError ||
34 std.fs.File.CopyRangeError ||56 Io.File.ReadPositionalError ||
35 std.fs.File.PReadError ||57 Io.File.WritePositionalError;
36 std.fs.File.PWriteError;
3758
38pub const FlushError = UpdateError;59pub const FlushError = UpdateError;
3960
40pub const RelocError =61pub const RelocError =
41 std.fs.File.PWriteError;62 Io.File.PWriteError;
4263
43pub const AddressSize = enum(u8) {64pub const AddressSize = enum(u8) {
44 @"32" = 4,65 @"32" = 4,
...@@ -135,11 +156,14 @@ const DebugInfo = struct {...@@ -135,11 +156,14 @@ const DebugInfo = struct {
135156
136 fn declAbbrevCode(debug_info: *DebugInfo, unit: Unit.Index, entry: Entry.Index) !AbbrevCode {157 fn declAbbrevCode(debug_info: *DebugInfo, unit: Unit.Index, entry: Entry.Index) !AbbrevCode {
137 const dwarf: *Dwarf = @fieldParentPtr("debug_info", debug_info);158 const dwarf: *Dwarf = @fieldParentPtr("debug_info", debug_info);
159 const comp = dwarf.bin_file.comp;
160 const io = comp.io;
138 const unit_ptr = debug_info.section.getUnit(unit);161 const unit_ptr = debug_info.section.getUnit(unit);
139 const entry_ptr = unit_ptr.getEntry(entry);162 const entry_ptr = unit_ptr.getEntry(entry);
140 if (entry_ptr.len < AbbrevCode.decl_bytes) return .null;163 if (entry_ptr.len < AbbrevCode.decl_bytes) return .null;
141 var abbrev_code_buf: [AbbrevCode.decl_bytes]u8 = undefined;164 var abbrev_code_buf: [AbbrevCode.decl_bytes]u8 = undefined;
142 if (try dwarf.getFile().?.preadAll(165 if (try dwarf.getFile().?.readPositionalAll(
166 io,
143 &abbrev_code_buf,167 &abbrev_code_buf,
144 debug_info.section.off(dwarf) + unit_ptr.off + unit_ptr.header_len + entry_ptr.off,168 debug_info.section.off(dwarf) + unit_ptr.off + unit_ptr.header_len + entry_ptr.off,
145 ) != abbrev_code_buf.len) return error.InputOutput;169 ) != abbrev_code_buf.len) return error.InputOutput;
...@@ -619,13 +643,10 @@ const Unit = struct {...@@ -619,13 +643,10 @@ const Unit = struct {
619643
620 fn move(unit: *Unit, sec: *Section, dwarf: *Dwarf, new_off: u32) UpdateError!void {644 fn move(unit: *Unit, sec: *Section, dwarf: *Dwarf, new_off: u32) UpdateError!void {
621 if (unit.off == new_off) return;645 if (unit.off == new_off) return;
622 const n = try dwarf.getFile().?.copyRangeAll(646 const comp = dwarf.bin_file.comp;
623 sec.off(dwarf) + unit.off,647 const io = comp.io;
624 dwarf.getFile().?,648 const file = dwarf.getFile().?;
625 sec.off(dwarf) + new_off,649 try link.File.copyRangeAll2(io, file, file, sec.off(dwarf) + unit.off, sec.off(dwarf) + new_off, unit.len);
626 unit.len,
627 );
628 if (n != unit.len) return error.InputOutput;
629 unit.off = new_off;650 unit.off = new_off;
630 }651 }
631652
...@@ -655,10 +676,14 @@ const Unit = struct {...@@ -655,10 +676,14 @@ const Unit = struct {
655676
656 fn replaceHeader(unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void {677 fn replaceHeader(unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void {
657 assert(contents.len == unit.header_len);678 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);
659 }682 }
660683
661 fn writeTrailer(unit: *Unit, sec: *Section, dwarf: *Dwarf) UpdateError!void {684 fn writeTrailer(unit: *Unit, sec: *Section, dwarf: *Dwarf) UpdateError!void {
685 const comp = dwarf.bin_file.comp;
686 const io = comp.io;
662 const start = unit.off + unit.header_len + if (unit.last.unwrap()) |last_entry| end: {687 const start = unit.off + unit.header_len + if (unit.last.unwrap()) |last_entry| end: {
663 const last_entry_ptr = unit.getEntry(last_entry);688 const last_entry_ptr = unit.getEntry(last_entry);
664 break :end last_entry_ptr.off + last_entry_ptr.len;689 break :end last_entry_ptr.off + last_entry_ptr.len;
...@@ -688,7 +713,7 @@ const Unit = struct {...@@ -688,7 +713,7 @@ const Unit = struct {
688 assert(fw.end == extended_op_bytes + op_len_bytes);713 assert(fw.end == extended_op_bytes + op_len_bytes);
689 fw.writeByte(DW.LNE.padding) catch unreachable;714 fw.writeByte(DW.LNE.padding) catch unreachable;
690 assert(fw.end >= unit.trailer_len and fw.end <= len);715 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);
692 }717 }
693 var trailer_aw: Writer.Allocating = try .initCapacity(dwarf.gpa, len);718 var trailer_aw: Writer.Allocating = try .initCapacity(dwarf.gpa, len);
694 defer trailer_aw.deinit();719 defer trailer_aw.deinit();
...@@ -748,7 +773,7 @@ const Unit = struct {...@@ -748,7 +773,7 @@ const Unit = struct {
748 assert(tw.end == unit.trailer_len);773 assert(tw.end == unit.trailer_len);
749 tw.splatByteAll(fill_byte, len - unit.trailer_len) catch unreachable;774 tw.splatByteAll(fill_byte, len - unit.trailer_len) catch unreachable;
750 assert(tw.end == len);775 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);
752 }777 }
753778
754 fn resolveRelocs(unit: *Unit, sec: *Section, dwarf: *Dwarf) RelocError!void {779 fn resolveRelocs(unit: *Unit, sec: *Section, dwarf: *Dwarf) RelocError!void {
...@@ -834,6 +859,8 @@ const Entry = struct {...@@ -834,6 +859,8 @@ const Entry = struct {
834 dwarf: *Dwarf,859 dwarf: *Dwarf,
835 ) (UpdateError || Writer.Error)!void {860 ) (UpdateError || Writer.Error)!void {
836 assert(entry.len > 0);861 assert(entry.len > 0);
862 const comp = dwarf.bin_file.comp;
863 const io = comp.io;
837 const start = entry.off + entry.len;864 const start = entry.off + entry.len;
838 if (sec == &dwarf.debug_frame.section) {865 if (sec == &dwarf.debug_frame.section) {
839 const len = if (entry.next.unwrap()) |next_entry|866 const len = if (entry.next.unwrap()) |next_entry|
...@@ -843,11 +870,11 @@ const Entry = struct {...@@ -843,11 +870,11 @@ const Entry = struct {
843 var unit_len_buf: [8]u8 = undefined;870 var unit_len_buf: [8]u8 = undefined;
844 const unit_len_bytes = unit_len_buf[0..dwarf.sectionOffsetBytes()];871 const unit_len_bytes = unit_len_buf[0..dwarf.sectionOffsetBytes()];
845 dwarf.writeInt(unit_len_bytes, len - dwarf.unitLengthBytes());872 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);
847 const buf = try dwarf.gpa.alloc(u8, len - entry.len);874 const buf = try dwarf.gpa.alloc(u8, len - entry.len);
848 defer dwarf.gpa.free(buf);875 defer dwarf.gpa.free(buf);
849 @memset(buf, DW.CFA.nop);876 @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);
851 return;878 return;
852 }879 }
853 const len = unit.getEntry(entry.next.unwrap() orelse return).off - start;880 const len = unit.getEntry(entry.next.unwrap() orelse return).off - start;
...@@ -906,7 +933,7 @@ const Entry = struct {...@@ -906,7 +933,7 @@ const Entry = struct {
906 },933 },
907 } else assert(!sec.pad_entries_to_ideal and len == 0);934 } else assert(!sec.pad_entries_to_ideal and len == 0);
908 assert(fw.end <= len);935 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);
910 }937 }
911938
912 fn resize(939 fn resize(
...@@ -949,11 +976,13 @@ const Entry = struct {...@@ -949,11 +976,13 @@ const Entry = struct {
949976
950 fn replace(entry_ptr: *Entry, unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void {977 fn replace(entry_ptr: *Entry, unit: *Unit, sec: *Section, dwarf: *Dwarf, contents: []const u8) UpdateError!void {
951 assert(contents.len == entry_ptr.len);978 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);
953 if (false) {982 if (false) {
954 const buf = try dwarf.gpa.alloc(u8, sec.len);983 const buf = try dwarf.gpa.alloc(u8, sec.len);
955 defer dwarf.gpa.free(buf);984 defer dwarf.gpa.free(buf);
956 _ = try dwarf.getFile().?.preadAll(buf, sec.off(dwarf));985 _ = try dwarf.getFile().?.readPositionalAll(io, buf, sec.off(dwarf));
957 log.info("Section{{ .first = {}, .last = {}, .off = 0x{x}, .len = 0x{x} }}", .{986 log.info("Section{{ .first = {}, .last = {}, .off = 0x{x}, .len = 0x{x} }}", .{
958 @intFromEnum(sec.first),987 @intFromEnum(sec.first),
959 @intFromEnum(sec.last),988 @intFromEnum(sec.last),
...@@ -4682,6 +4711,8 @@ fn updateContainerTypeWriterError(...@@ -4682,6 +4711,8 @@ fn updateContainerTypeWriterError(
4682}4711}
46834712
4684pub fn updateLineNumber(dwarf: *Dwarf, zcu: *Zcu, zir_index: InternPool.TrackedInst.Index) UpdateError!void {4713pub 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;
4685 const ip = &zcu.intern_pool;4716 const ip = &zcu.intern_pool;
46864717
4687 const inst_info = zir_index.resolveFull(ip).?;4718 const inst_info = zir_index.resolveFull(ip).?;
...@@ -4701,7 +4732,7 @@ pub fn updateLineNumber(dwarf: *Dwarf, zcu: *Zcu, zir_index: InternPool.TrackedI...@@ -4701,7 +4732,7 @@ pub fn updateLineNumber(dwarf: *Dwarf, zcu: *Zcu, zir_index: InternPool.TrackedI
47014732
4702 const unit = dwarf.debug_info.section.getUnit(dwarf.getUnitIfExists(file.mod.?) orelse return);4733 const unit = dwarf.debug_info.section.getUnit(dwarf.getUnitIfExists(file.mod.?) orelse return);
4703 const entry = unit.getEntry(dwarf.decls.get(zir_index) orelse return);4734 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));
4705}4736}
47064737
4707pub fn freeNav(dwarf: *Dwarf, nav_index: InternPool.Nav.Index) void {4738pub fn freeNav(dwarf: *Dwarf, nav_index: InternPool.Nav.Index) void {
...@@ -4738,6 +4769,8 @@ pub fn flush(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void {...@@ -4738,6 +4769,8 @@ pub fn flush(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void {
4738fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Error)!void {4769fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Error)!void {
4739 const zcu = pt.zcu;4770 const zcu = pt.zcu;
4740 const ip = &zcu.intern_pool;4771 const ip = &zcu.intern_pool;
4772 const comp = dwarf.bin_file.comp;
4773 const io = comp.io;
47414774
4742 {4775 {
4743 const type_gop = try dwarf.types.getOrPut(dwarf.gpa, .anyerror_type);4776 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...@@ -4957,7 +4990,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro
4957 if (dwarf.debug_str.section.dirty) {4990 if (dwarf.debug_str.section.dirty) {
4958 const contents = dwarf.debug_str.contents.items;4991 const contents = dwarf.debug_str.contents.items;
4959 try dwarf.debug_str.section.resize(dwarf, contents.len);4992 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));
4961 dwarf.debug_str.section.dirty = false;4994 dwarf.debug_str.section.dirty = false;
4962 }4995 }
4963 if (dwarf.debug_line.section.dirty) {4996 if (dwarf.debug_line.section.dirty) {
...@@ -5069,7 +5102,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro...@@ -5069,7 +5102,7 @@ fn flushWriterError(dwarf: *Dwarf, pt: Zcu.PerThread) (FlushError || Writer.Erro
5069 if (dwarf.debug_line_str.section.dirty) {5102 if (dwarf.debug_line_str.section.dirty) {
5070 const contents = dwarf.debug_line_str.contents.items;5103 const contents = dwarf.debug_line_str.contents.items;
5071 try dwarf.debug_line_str.section.resize(dwarf, contents.len);5104 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));
5073 dwarf.debug_line_str.section.dirty = false;5106 dwarf.debug_line_str.section.dirty = false;
5074 }5107 }
5075 if (dwarf.debug_loclists.section.dirty) {5108 if (dwarf.debug_loclists.section.dirty) {
...@@ -6350,7 +6383,7 @@ const AbbrevCode = enum {...@@ -6350,7 +6383,7 @@ const AbbrevCode = enum {
6350 });6383 });
6351};6384};
63526385
6353fn getFile(dwarf: *Dwarf) ?std.fs.File {6386fn getFile(dwarf: *Dwarf) ?Io.File {
6354 if (dwarf.bin_file.cast(.macho)) |macho_file| if (macho_file.d_sym) |*d_sym| return d_sym.file;6387 if (dwarf.bin_file.cast(.macho)) |macho_file| if (macho_file.d_sym) |*d_sym| return d_sym.file;
6355 return dwarf.bin_file.file;6388 return dwarf.bin_file.file;
6356}6389}
...@@ -6391,9 +6424,11 @@ fn writeInt(dwarf: *Dwarf, buf: []u8, int: u64) void {...@@ -6391,9 +6424,11 @@ fn writeInt(dwarf: *Dwarf, buf: []u8, int: u64) void {
6391}6424}
63926425
6393fn resolveReloc(dwarf: *Dwarf, source: u64, target: u64, size: u32) RelocError!void {6426fn resolveReloc(dwarf: *Dwarf, source: u64, target: u64, size: u32) RelocError!void {
6427 const comp = dwarf.bin_file.comp;
6428 const io = comp.io;
6394 var buf: [8]u8 = undefined;6429 var buf: [8]u8 = undefined;
6395 dwarf.writeInt(buf[0..size], target);6430 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);
6397}6432}
63986433
6399fn unitLengthBytes(dwarf: *Dwarf) u32 {6434fn unitLengthBytes(dwarf: *Dwarf) u32 {
...@@ -6429,21 +6464,3 @@ const force_incremental = false;...@@ -6429,21 +6464,3 @@ const force_incremental = false;
6429inline fn incremental(dwarf: Dwarf) bool {6464inline fn incremental(dwarf: Dwarf) bool {
6430 return force_incremental or dwarf.bin_file.comp.config.incremental;6465 return force_incremental or dwarf.bin_file.comp.config.incremental;
6431}6466}
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(...@@ -313,12 +313,14 @@ pub fn createEmpty(
313 const is_obj = output_mode == .Obj;313 const is_obj = output_mode == .Obj;
314 const is_obj_or_ar = is_obj or (output_mode == .Lib and link_mode == .static);314 const is_obj_or_ar = is_obj or (output_mode == .Lib and link_mode == .static);
315315
316 const io = comp.io;
317
316 // What path should this ELF linker code output to?318 // What path should this ELF linker code output to?
317 const sub_path = emit.sub_path;319 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, .{
319 .truncate = true,321 .truncate = true,
320 .read = true,322 .read = true,
321 .mode = link.File.determineMode(output_mode, link_mode),323 .permissions = link.File.determinePermissions(output_mode, link_mode),
322 });324 });
323325
324 const gpa = comp.gpa;326 const gpa = comp.gpa;
...@@ -406,10 +408,12 @@ pub fn open(...@@ -406,10 +408,12 @@ pub fn open(
406}408}
407409
408pub fn deinit(self: *Elf) void {410pub 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
411 for (self.file_handles.items) |fh| {415 for (self.file_handles.items) |fh| {
412 fh.close();416 fh.close(io);
413 }417 }
414 self.file_handles.deinit(gpa);418 self.file_handles.deinit(gpa);
415419
...@@ -483,6 +487,8 @@ pub fn getUavVAddr(self: *Elf, uav: InternPool.Index, reloc_info: link.File.Relo...@@ -483,6 +487,8 @@ pub fn getUavVAddr(self: *Elf, uav: InternPool.Index, reloc_info: link.File.Relo
483487
484/// Returns end pos of collision, if any.488/// Returns end pos of collision, if any.
485fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 {489fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 {
490 const comp = self.base.comp;
491 const io = comp.io;
486 const small_ptr = self.ptr_width == .p32;492 const small_ptr = self.ptr_width == .p32;
487 const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);493 const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);
488 if (start < ehdr_size)494 if (start < ehdr_size)
...@@ -522,7 +528,7 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 {...@@ -522,7 +528,7 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) !?u64 {
522 }528 }
523 }529 }
524530
525 if (at_end) try self.base.file.?.setEndPos(end);531 if (at_end) try self.base.file.?.setLength(io, end);
526 return null;532 return null;
527}533}
528534
...@@ -552,6 +558,8 @@ pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u64) !u64 {...@@ -552,6 +558,8 @@ pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u64) !u64 {
552}558}
553559
554pub fn growSection(self: *Elf, shdr_index: u32, needed_size: u64, min_alignment: u64) !void {560pub 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;
555 const shdr = &self.sections.items(.shdr)[shdr_index];563 const shdr = &self.sections.items(.shdr)[shdr_index];
556564
557 if (shdr.sh_type != elf.SHT_NOBITS) {565 if (shdr.sh_type != elf.SHT_NOBITS) {
...@@ -574,18 +582,11 @@ pub fn growSection(self: *Elf, shdr_index: u32, needed_size: u64, min_alignment:...@@ -574,18 +582,11 @@ pub fn growSection(self: *Elf, shdr_index: u32, needed_size: u64, min_alignment:
574 new_offset,582 new_offset,
575 });583 });
576584
577 const amt = try self.base.file.?.copyRangeAll(585 try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size);
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;
585586
586 shdr.sh_offset = new_offset;587 shdr.sh_offset = new_offset;
587 } else if (shdr.sh_offset + allocated_size == std.math.maxInt(u64)) {588 } 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);
589 }590 }
590 }591 }
591592
...@@ -737,8 +738,8 @@ pub fn loadInput(self: *Elf, input: link.Input) !void {...@@ -737,8 +738,8 @@ pub fn loadInput(self: *Elf, input: link.Input) !void {
737 .res => unreachable,738 .res => unreachable,
738 .dso_exact => @panic("TODO"),739 .dso_exact => @panic("TODO"),
739 .object => |obj| try parseObject(self, obj),740 .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 .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),
741 .dso => |dso| try parseDso(gpa, diags, dso, &self.shared_objects, &self.files, target),742 .dso => |dso| try parseDso(gpa, io, diags, dso, &self.shared_objects, &self.files, target),
742 }743 }
743}744}
744745
...@@ -747,9 +748,10 @@ pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std...@@ -747,9 +748,10 @@ pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std
747 defer tracy.end();748 defer tracy.end();
748749
749 const comp = self.base.comp;750 const comp = self.base.comp;
751 const io = comp.io;
750 const diags = &comp.link_diags;752 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
754 const sub_prog_node = prog_node.start("ELF Flush", 0);756 const sub_prog_node = prog_node.start("ELF Flush", 0);
755 defer sub_prog_node.end();757 defer sub_prog_node.end();
...@@ -757,7 +759,7 @@ pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std...@@ -757,7 +759,7 @@ pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std
757 return flushInner(self, arena, tid) catch |err| switch (err) {759 return flushInner(self, arena, tid) catch |err| switch (err) {
758 error.OutOfMemory => return error.OutOfMemory,760 error.OutOfMemory => return error.OutOfMemory,
759 error.LinkFailure => return error.LinkFailure,761 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}),
761 };763 };
762}764}
763765
...@@ -1047,9 +1049,11 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void {...@@ -1047,9 +1049,11 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void {
1047}1049}
10481050
1049pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {1051pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {
1050 const diags = &self.base.comp.link_diags;1052 const comp = self.base.comp;
1051 const obj = link.openObject(path, false, false) catch |err| {1053 const io = comp.io;
1052 switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) {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})) {
1053 error.LinkFailure => return,1057 error.LinkFailure => return,
1054 }1058 }
1055 };1059 };
...@@ -1057,10 +1061,11 @@ pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {...@@ -1057,10 +1061,11 @@ pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {
1057}1061}
10581062
1059fn parseObjectReportingFailure(self: *Elf, obj: link.Input.Object) void {1063fn 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;
1061 self.parseObject(obj) catch |err| switch (err) {1066 self.parseObject(obj) catch |err| switch (err) {
1062 error.LinkFailure => return, // already reported1067 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}),
1064 };1069 };
1065}1070}
10661071
...@@ -1068,10 +1073,12 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void {...@@ -1068,10 +1073,12 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void {
1068 const tracy = trace(@src());1073 const tracy = trace(@src());
1069 defer tracy.end();1074 defer tracy.end();
10701075
1071 const gpa = self.base.comp.gpa;1076 const comp = self.base.comp;
1072 const diags = &self.base.comp.link_diags;1077 const io = comp.io;
1073 const target = &self.base.comp.root_mod.resolved_target.result;1078 const gpa = comp.gpa;
1074 const debug_fmt_strip = self.base.comp.config.debug_format == .strip;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;
1075 const default_sym_version = self.default_sym_version;1082 const default_sym_version = self.default_sym_version;
1076 const file_handles = &self.file_handles;1083 const file_handles = &self.file_handles;
10771084
...@@ -1090,14 +1097,15 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void {...@@ -1090,14 +1097,15 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void {
1090 try self.objects.append(gpa, index);1097 try self.objects.append(gpa, index);
10911098
1092 const object = self.file(index).?.object;1099 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);
1094 if (!self.base.isStaticLib()) {1101 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);
1096 }1103 }
1097}1104}
10981105
1099fn parseArchive(1106fn parseArchive(
1100 gpa: Allocator,1107 gpa: Allocator,
1108 io: Io,
1101 diags: *Diags,1109 diags: *Diags,
1102 file_handles: *std.ArrayList(File.Handle),1110 file_handles: *std.ArrayList(File.Handle),
1103 files: *std.MultiArrayList(File.Entry),1111 files: *std.MultiArrayList(File.Entry),
...@@ -1112,7 +1120,7 @@ fn parseArchive(...@@ -1112,7 +1120,7 @@ fn parseArchive(
1112 defer tracy.end();1120 defer tracy.end();
11131121
1114 const fh = try addFileHandle(gpa, file_handles, obj.file);1122 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);
1116 defer archive.deinit(gpa);1124 defer archive.deinit(gpa);
11171125
1118 const init_alive = if (is_static_lib) true else obj.must_link;1126 const init_alive = if (is_static_lib) true else obj.must_link;
...@@ -1123,15 +1131,16 @@ fn parseArchive(...@@ -1123,15 +1131,16 @@ fn parseArchive(
1123 const object = &files.items(.data)[index].object;1131 const object = &files.items(.data)[index].object;
1124 object.index = index;1132 object.index = index;
1125 object.alive = init_alive;1133 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);
1127 if (!is_static_lib)1135 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);
1129 try objects.append(gpa, index);1137 try objects.append(gpa, index);
1130 }1138 }
1131}1139}
11321140
1133fn parseDso(1141fn parseDso(
1134 gpa: Allocator,1142 gpa: Allocator,
1143 io: Io,
1135 diags: *Diags,1144 diags: *Diags,
1136 dso: link.Input.Dso,1145 dso: link.Input.Dso,
1137 shared_objects: *std.StringArrayHashMapUnmanaged(File.Index),1146 shared_objects: *std.StringArrayHashMapUnmanaged(File.Index),
...@@ -1143,8 +1152,8 @@ fn parseDso(...@@ -1143,8 +1152,8 @@ fn parseDso(
11431152
1144 const handle = dso.file;1153 const handle = dso.file;
11451154
1146 const stat = Stat.fromFs(try handle.stat());1155 const stat = Stat.fromFs(try handle.stat(io));
1147 var header = try SharedObject.parseHeader(gpa, diags, dso.path, handle, stat, target);1156 var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target);
1148 defer header.deinit(gpa);1157 defer header.deinit(gpa);
11491158
1150 const soname = header.soname() orelse dso.path.basename();1159 const soname = header.soname() orelse dso.path.basename();
...@@ -1158,7 +1167,7 @@ fn parseDso(...@@ -1158,7 +1167,7 @@ fn parseDso(
11581167
1159 gop.value_ptr.* = index;1168 gop.value_ptr.* = index;
11601169
1161 var parsed = try SharedObject.parse(gpa, &header, handle);1170 var parsed = try SharedObject.parse(gpa, io, &header, handle);
1162 errdefer parsed.deinit(gpa);1171 errdefer parsed.deinit(gpa);
11631172
1164 const duped_path: Path = .{1173 const duped_path: Path = .{
...@@ -2888,13 +2897,7 @@ pub fn allocateAllocSections(self: *Elf) !void {...@@ -2888,13 +2897,7 @@ pub fn allocateAllocSections(self: *Elf) !void {
2888 if (shdr.sh_offset > 0) {2897 if (shdr.sh_offset > 0) {
2889 // Get size actually commited to the output file.2898 // Get size actually commited to the output file.
2890 const existing_size = self.sectionSize(shndx);2899 const existing_size = self.sectionSize(shndx);
2891 const amt = try self.base.file.?.copyRangeAll(2900 try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size);
2892 shdr.sh_offset,
2893 self.base.file.?,
2894 new_offset,
2895 existing_size,
2896 );
2897 if (amt != existing_size) return error.InputOutput;
2898 }2901 }
28992902
2900 shdr.sh_offset = new_offset;2903 shdr.sh_offset = new_offset;
...@@ -2930,13 +2933,7 @@ pub fn allocateNonAllocSections(self: *Elf) !void {...@@ -2930,13 +2933,7 @@ pub fn allocateNonAllocSections(self: *Elf) !void {
29302933
2931 if (shdr.sh_offset > 0) {2934 if (shdr.sh_offset > 0) {
2932 const existing_size = self.sectionSize(@intCast(shndx));2935 const existing_size = self.sectionSize(@intCast(shndx));
2933 const amt = try self.base.file.?.copyRangeAll(2936 try self.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size);
2934 shdr.sh_offset,
2935 self.base.file.?,
2936 new_offset,
2937 existing_size,
2938 );
2939 if (amt != existing_size) return error.InputOutput;
2940 }2937 }
29412938
2942 shdr.sh_offset = new_offset;2939 shdr.sh_offset = new_offset;
...@@ -3649,7 +3646,7 @@ fn fileLookup(files: std.MultiArrayList(File.Entry), index: File.Index, zig_obje...@@ -3649,7 +3646,7 @@ fn fileLookup(files: std.MultiArrayList(File.Entry), index: File.Index, zig_obje
3649pub fn addFileHandle(3646pub fn addFileHandle(
3650 gpa: Allocator,3647 gpa: Allocator,
3651 file_handles: *std.ArrayList(File.Handle),3648 file_handles: *std.ArrayList(File.Handle),
3652 handle: fs.File,3649 handle: Io.File,
3653) Allocator.Error!File.HandleIndex {3650) Allocator.Error!File.HandleIndex {
3654 try file_handles.append(gpa, handle);3651 try file_handles.append(gpa, handle);
3655 return @intCast(file_handles.items.len - 1);3652 return @intCast(file_handles.items.len - 1);
...@@ -4066,10 +4063,10 @@ fn fmtDumpState(self: *Elf, writer: *std.Io.Writer) std.Io.Writer.Error!void {...@@ -4066,10 +4063,10 @@ fn fmtDumpState(self: *Elf, writer: *std.Io.Writer) std.Io.Writer.Error!void {
4066}4063}
40674064
4068/// Caller owns the memory.4065/// 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 {
4070 const buffer = try allocator.alloc(u8, math.cast(usize, size) orelse return error.Overflow);4067 const buffer = try allocator.alloc(u8, math.cast(usize, size) orelse return error.Overflow);
4071 errdefer allocator.free(buffer);4068 errdefer allocator.free(buffer);
4072 const amt = try handle.preadAll(buffer, offset);4069 const amt = try io_file.readPositionalAll(io, buffer, offset);
4073 if (amt != size) return error.InputOutput;4070 if (amt != size) return error.InputOutput;
4074 return buffer;4071 return buffer;
4075}4072}
...@@ -4435,16 +4432,17 @@ pub fn stringTableLookup(strtab: []const u8, off: u32) [:0]const u8 {...@@ -4435,16 +4432,17 @@ pub fn stringTableLookup(strtab: []const u8, off: u32) [:0]const u8 {
44354432
4436pub fn pwriteAll(elf_file: *Elf, bytes: []const u8, offset: u64) error{LinkFailure}!void {4433pub fn pwriteAll(elf_file: *Elf, bytes: []const u8, offset: u64) error{LinkFailure}!void {
4437 const comp = elf_file.base.comp;4434 const comp = elf_file.base.comp;
4435 const io = comp.io;
4438 const diags = &comp.link_diags;4436 const diags = &comp.link_diags;
4439 elf_file.base.file.?.pwriteAll(bytes, offset) catch |err| {4437 elf_file.base.file.?.writePositionalAll(io, bytes, offset) catch |err|
4440 return diags.fail("failed to write: {s}", .{@errorName(err)});4438 return diags.fail("failed to write: {t}", .{err});
4441 };
4442}4439}
44434440
4444pub fn setEndPos(elf_file: *Elf, length: u64) error{LinkFailure}!void {4441pub fn setLength(elf_file: *Elf, length: u64) error{LinkFailure}!void {
4445 const comp = elf_file.base.comp;4442 const comp = elf_file.base.comp;
4443 const io = comp.i;
4446 const diags = &comp.link_diags;4444 const diags = &comp.link_diags;
4447 elf_file.base.file.?.setEndPos(length) catch |err| {4445 elf_file.base.file.?.setLength(io, length) catch |err| {
4448 return diags.fail("failed to set file end pos: {s}", .{@errorName(err)});4446 return diags.fail("failed to set file end pos: {s}", .{@errorName(err)});
4449 };4447 };
4450}4448}
...@@ -4458,6 +4456,7 @@ pub fn cast(elf_file: *Elf, comptime T: type, x: anytype) error{LinkFailure}!T {...@@ -4458,6 +4456,7 @@ pub fn cast(elf_file: *Elf, comptime T: type, x: anytype) error{LinkFailure}!T {
4458}4456}
44594457
4460const std = @import("std");4458const std = @import("std");
4459const Io = std.Io;
4461const build_options = @import("build_options");4460const build_options = @import("build_options");
4462const builtin = @import("builtin");4461const builtin = @import("builtin");
4463const assert = std.debug.assert;4462const assert = std.debug.assert;
src/link/Elf/Archive.zig+28-25
...@@ -1,3 +1,21 @@...@@ -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
1objects: []const Object,19objects: []const Object,
2/// '\n'-delimited20/// '\n'-delimited
3strtab: []const u8,21strtab: []const u8,
...@@ -10,22 +28,23 @@ pub fn deinit(a: *Archive, gpa: Allocator) void {...@@ -10,22 +28,23 @@ pub fn deinit(a: *Archive, gpa: Allocator) void {
1028
11pub fn parse(29pub fn parse(
12 gpa: Allocator,30 gpa: Allocator,
31 io: Io,
13 diags: *Diags,32 diags: *Diags,
14 file_handles: *const std.ArrayList(File.Handle),33 file_handles: *const std.ArrayList(File.Handle),
15 path: Path,34 path: Path,
16 handle_index: File.HandleIndex,35 handle_index: File.HandleIndex,
17) !Archive {36) !Archive {
18 const handle = file_handles.items[handle_index];37 const file = file_handles.items[handle_index];
19 var pos: usize = 0;38 var pos: usize = 0;
20 {39 {
21 var magic_buffer: [elf.ARMAG.len]u8 = undefined;40 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);
23 if (n != magic_buffer.len) return error.BadMagic;42 if (n != magic_buffer.len) return error.BadMagic;
24 if (!mem.eql(u8, &magic_buffer, elf.ARMAG)) return error.BadMagic;43 if (!mem.eql(u8, &magic_buffer, elf.ARMAG)) return error.BadMagic;
25 pos += magic_buffer.len;44 pos += magic_buffer.len;
26 }45 }
2746
28 const size = (try handle.stat()).size;47 const size = (try file.stat(io)).size;
2948
30 var objects: std.ArrayList(Object) = .empty;49 var objects: std.ArrayList(Object) = .empty;
31 defer objects.deinit(gpa);50 defer objects.deinit(gpa);
...@@ -36,7 +55,7 @@ pub fn parse(...@@ -36,7 +55,7 @@ pub fn parse(
36 while (pos < size) {55 while (pos < size) {
37 var hdr: elf.ar_hdr = undefined;56 var hdr: elf.ar_hdr = undefined;
38 {57 {
39 const n = try handle.preadAll(mem.asBytes(&hdr), pos);58 const n = try file.readPositionalAll(io, mem.asBytes(&hdr), pos);
40 if (n != @sizeOf(elf.ar_hdr)) return error.UnexpectedEndOfFile;59 if (n != @sizeOf(elf.ar_hdr)) return error.UnexpectedEndOfFile;
41 }60 }
42 pos += @sizeOf(elf.ar_hdr);61 pos += @sizeOf(elf.ar_hdr);
...@@ -53,7 +72,7 @@ pub fn parse(...@@ -53,7 +72,7 @@ pub fn parse(
53 if (hdr.isSymtab() or hdr.isSymtab64()) continue;72 if (hdr.isSymtab() or hdr.isSymtab64()) continue;
54 if (hdr.isStrtab()) {73 if (hdr.isStrtab()) {
55 try strtab.resize(gpa, obj_size);74 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);
57 if (amt != obj_size) return error.InputOutput;76 if (amt != obj_size) return error.InputOutput;
58 continue;77 continue;
59 }78 }
...@@ -120,7 +139,7 @@ pub fn setArHdr(opts: struct {...@@ -120,7 +139,7 @@ pub fn setArHdr(opts: struct {
120 @memset(mem.asBytes(&hdr), 0x20);139 @memset(mem.asBytes(&hdr), 0x20);
121140
122 {141 {
123 var writer: std.Io.Writer = .fixed(&hdr.ar_name);142 var writer: Io.Writer = .fixed(&hdr.ar_name);
124 switch (opts.name) {143 switch (opts.name) {
125 .symtab => writer.print("{s}", .{elf.SYM64NAME}) catch unreachable,144 .symtab => writer.print("{s}", .{elf.SYM64NAME}) catch unreachable,
126 .strtab => writer.print("//", .{}) catch unreachable,145 .strtab => writer.print("//", .{}) catch unreachable,
...@@ -133,7 +152,7 @@ pub fn setArHdr(opts: struct {...@@ -133,7 +152,7 @@ pub fn setArHdr(opts: struct {
133 hdr.ar_gid[0] = '0';152 hdr.ar_gid[0] = '0';
134 hdr.ar_mode[0] = '0';153 hdr.ar_mode[0] = '0';
135 {154 {
136 var writer: std.Io.Writer = .fixed(&hdr.ar_size);155 var writer: Io.Writer = .fixed(&hdr.ar_size);
137 writer.print("{d}", .{opts.size}) catch unreachable;156 writer.print("{d}", .{opts.size}) catch unreachable;
138 }157 }
139 hdr.ar_fmag = elf.ARFMAG.*;158 hdr.ar_fmag = elf.ARFMAG.*;
...@@ -206,7 +225,7 @@ pub const ArSymtab = struct {...@@ -206,7 +225,7 @@ pub const ArSymtab = struct {
206 ar: ArSymtab,225 ar: ArSymtab,
207 elf_file: *Elf,226 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 {
210 const ar = f.ar;229 const ar = f.ar;
211 const elf_file = f.elf_file;230 const elf_file = f.elf_file;
212 for (ar.symtab.items, 0..) |entry, i| {231 for (ar.symtab.items, 0..) |entry, i| {
...@@ -261,7 +280,7 @@ pub const ArStrtab = struct {...@@ -261,7 +280,7 @@ pub const ArStrtab = struct {
261 try writer.writeAll(ar.buffer.items);280 try writer.writeAll(ar.buffer.items);
262 }281 }
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 {
265 try writer.print("{f}", .{std.ascii.hexEscape(ar.buffer.items, .lower)});284 try writer.print("{f}", .{std.ascii.hexEscape(ar.buffer.items, .lower)});
266 }285 }
267};286};
...@@ -277,19 +296,3 @@ pub const ArState = struct {...@@ -277,19 +296,3 @@ pub const ArState = struct {
277 /// Total size of the contributing object (excludes ar_hdr).296 /// Total size of the contributing object (excludes ar_hdr).
278 size: u64 = 0,297 size: u64 = 0,
279};298};
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 {...@@ -90,7 +90,9 @@ pub fn allocate(list: *AtomList, elf_file: *Elf) !void {
90}90}
9191
92pub fn write(list: AtomList, buffer: *std.Io.Writer.Allocating, undefs: anytype, elf_file: *Elf) !void {92pub 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;
94 const osec = elf_file.sections.items(.shdr)[list.output_section_index];96 const osec = elf_file.sections.items(.shdr)[list.output_section_index];
95 assert(osec.sh_type != elf.SHT_NOBITS);97 assert(osec.sh_type != elf.SHT_NOBITS);
96 assert(!list.dirty);98 assert(!list.dirty);
...@@ -121,12 +123,14 @@ pub fn write(list: AtomList, buffer: *std.Io.Writer.Allocating, undefs: anytype,...@@ -121,12 +123,14 @@ pub fn write(list: AtomList, buffer: *std.Io.Writer.Allocating, undefs: anytype,
121 try atom_ptr.resolveRelocsAlloc(elf_file, out_code);123 try atom_ptr.resolveRelocsAlloc(elf_file, out_code);
122 }124 }
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));
125 buffer.clearRetainingCapacity();127 buffer.clearRetainingCapacity();
126}128}
127129
128pub fn writeRelocatable(list: AtomList, buffer: *std.array_list.Managed(u8), elf_file: *Elf) !void {130pub 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;
130 const osec = elf_file.sections.items(.shdr)[list.output_section_index];134 const osec = elf_file.sections.items(.shdr)[list.output_section_index];
131 assert(osec.sh_type != elf.SHT_NOBITS);135 assert(osec.sh_type != elf.SHT_NOBITS);
132136
...@@ -152,7 +156,7 @@ pub fn writeRelocatable(list: AtomList, buffer: *std.array_list.Managed(u8), elf...@@ -152,7 +156,7 @@ pub fn writeRelocatable(list: AtomList, buffer: *std.array_list.Managed(u8), elf
152 @memcpy(out_code, code);156 @memcpy(out_code, code);
153 }157 }
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));
156 buffer.clearRetainingCapacity();160 buffer.clearRetainingCapacity();
157}161}
158162
src/link/Elf/Object.zig+61-51
...@@ -1,3 +1,30 @@...@@ -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
1archive: ?InArchive = null,28archive: ?InArchive = null,
2/// Archive files cannot contain subdirectories, so only the basename is needed29/// Archive files cannot contain subdirectories, so only the basename is needed
3/// for output. However, the full path is kept for error reporting.30/// for output. However, the full path is kept for error reporting.
...@@ -65,10 +92,11 @@ pub fn deinit(self: *Object, gpa: Allocator) void {...@@ -65,10 +92,11 @@ pub fn deinit(self: *Object, gpa: Allocator) void {
65pub fn parse(92pub fn parse(
66 self: *Object,93 self: *Object,
67 gpa: Allocator,94 gpa: Allocator,
95 io: Io,
68 diags: *Diags,96 diags: *Diags,
69 /// For error reporting purposes only.97 /// For error reporting purposes only.
70 path: Path,98 path: Path,
71 handle: fs.File,99 handle: Io.File,
72 target: *const std.Target,100 target: *const std.Target,
73 debug_fmt_strip: bool,101 debug_fmt_strip: bool,
74 default_sym_version: elf.Versym,102 default_sym_version: elf.Versym,
...@@ -78,7 +106,7 @@ pub fn parse(...@@ -78,7 +106,7 @@ pub fn parse(
78 // Allocate atom index 0 to null atom106 // Allocate atom index 0 to null atom
79 try self.atoms.append(gpa, .{ .extra_index = try self.addAtomExtra(gpa, .{}) });107 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);
82 try self.initSymbols(gpa, default_sym_version);110 try self.initSymbols(gpa, default_sym_version);
83111
84 for (self.shdrs.items, 0..) |shdr, i| {112 for (self.shdrs.items, 0..) |shdr, i| {
...@@ -87,7 +115,7 @@ pub fn parse(...@@ -87,7 +115,7 @@ pub fn parse(
87 if ((target.cpu.arch == .x86_64 and shdr.sh_type == elf.SHT_X86_64_UNWIND) or115 if ((target.cpu.arch == .x86_64 and shdr.sh_type == elf.SHT_X86_64_UNWIND) or
88 mem.eql(u8, self.getString(atom_ptr.name_offset), ".eh_frame"))116 mem.eql(u8, self.getString(atom_ptr.name_offset), ".eh_frame"))
89 {117 {
90 try self.parseEhFrame(gpa, handle, @intCast(i), target);118 try self.parseEhFrame(gpa, io, handle, @intCast(i), target);
91 }119 }
92 }120 }
93}121}
...@@ -95,15 +123,16 @@ pub fn parse(...@@ -95,15 +123,16 @@ pub fn parse(
95pub fn parseCommon(123pub fn parseCommon(
96 self: *Object,124 self: *Object,
97 gpa: Allocator,125 gpa: Allocator,
126 io: Io,
98 diags: *Diags,127 diags: *Diags,
99 path: Path,128 path: Path,
100 handle: fs.File,129 handle: Io.File,
101 target: *const std.Target,130 target: *const std.Target,
102) !void {131) !void {
103 const offset = if (self.archive) |ar| ar.offset else 0;132 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));
107 defer gpa.free(header_buffer);136 defer gpa.free(header_buffer);
108 self.header = @as(*align(1) const elf.Elf64_Ehdr, @ptrCast(header_buffer)).*;137 self.header = @as(*align(1) const elf.Elf64_Ehdr, @ptrCast(header_buffer)).*;
109 if (!mem.eql(u8, self.header.?.e_ident[0..4], elf.MAGIC)) {138 if (!mem.eql(u8, self.header.?.e_ident[0..4], elf.MAGIC)) {
...@@ -127,7 +156,7 @@ pub fn parseCommon(...@@ -127,7 +156,7 @@ pub fn parseCommon(
127 return diags.failParse(path, "corrupt header: section header table extends past the end of file", .{});156 return diags.failParse(path, "corrupt header: section header table extends past the end of file", .{});
128 }157 }
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);
131 defer gpa.free(shdrs_buffer);160 defer gpa.free(shdrs_buffer);
132 const shdrs = @as([*]align(1) const elf.Elf64_Shdr, @ptrCast(shdrs_buffer.ptr))[0..shnum];161 const shdrs = @as([*]align(1) const elf.Elf64_Shdr, @ptrCast(shdrs_buffer.ptr))[0..shnum];
133 try self.shdrs.appendUnalignedSlice(gpa, shdrs);162 try self.shdrs.appendUnalignedSlice(gpa, shdrs);
...@@ -140,7 +169,7 @@ pub fn parseCommon(...@@ -140,7 +169,7 @@ pub fn parseCommon(
140 }169 }
141 }170 }
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);
144 defer gpa.free(shstrtab);173 defer gpa.free(shstrtab);
145 for (self.shdrs.items) |shdr| {174 for (self.shdrs.items) |shdr| {
146 if (shdr.sh_name >= shstrtab.len) {175 if (shdr.sh_name >= shstrtab.len) {
...@@ -158,7 +187,7 @@ pub fn parseCommon(...@@ -158,7 +187,7 @@ pub fn parseCommon(
158 const shdr = self.shdrs.items[index];187 const shdr = self.shdrs.items[index];
159 self.first_global = shdr.sh_info;188 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);
162 defer gpa.free(raw_symtab);191 defer gpa.free(raw_symtab);
163 const nsyms = math.divExact(usize, raw_symtab.len, @sizeOf(elf.Elf64_Sym)) catch {192 const nsyms = math.divExact(usize, raw_symtab.len, @sizeOf(elf.Elf64_Sym)) catch {
164 return diags.failParse(path, "symbol table not evenly divisible", .{});193 return diags.failParse(path, "symbol table not evenly divisible", .{});
...@@ -166,7 +195,7 @@ pub fn parseCommon(...@@ -166,7 +195,7 @@ pub fn parseCommon(
166 const symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(raw_symtab.ptr))[0..nsyms];195 const symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(raw_symtab.ptr))[0..nsyms];
167196
168 const strtab_bias = @as(u32, @intCast(self.strtab.items.len));197 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);
170 defer gpa.free(strtab);199 defer gpa.free(strtab);
171 try self.strtab.appendSlice(gpa, strtab);200 try self.strtab.appendSlice(gpa, strtab);
172201
...@@ -262,9 +291,10 @@ pub fn validateEFlags(...@@ -262,9 +291,10 @@ pub fn validateEFlags(
262fn initAtoms(291fn initAtoms(
263 self: *Object,292 self: *Object,
264 gpa: Allocator,293 gpa: Allocator,
294 io: Io,
265 diags: *Diags,295 diags: *Diags,
266 path: Path,296 path: Path,
267 handle: fs.File,297 handle: Io.File,
268 debug_fmt_strip: bool,298 debug_fmt_strip: bool,
269 target: *const std.Target,299 target: *const std.Target,
270) !void {300) !void {
...@@ -297,7 +327,7 @@ fn initAtoms(...@@ -297,7 +327,7 @@ fn initAtoms(
297 };327 };
298328
299 const shndx: u32 = @intCast(i);329 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);
301 defer gpa.free(group_raw_data);331 defer gpa.free(group_raw_data);
302 const group_nmembers = math.divExact(usize, group_raw_data.len, @sizeOf(u32)) catch {332 const group_nmembers = math.divExact(usize, group_raw_data.len, @sizeOf(u32)) catch {
303 return diags.failParse(path, "corrupt section group: not evenly divisible ", .{});333 return diags.failParse(path, "corrupt section group: not evenly divisible ", .{});
...@@ -338,7 +368,7 @@ fn initAtoms(...@@ -338,7 +368,7 @@ fn initAtoms(
338 const shndx: u32 = @intCast(i);368 const shndx: u32 = @intCast(i);
339 if (self.skipShdr(shndx, debug_fmt_strip)) continue;369 if (self.skipShdr(shndx, debug_fmt_strip)) continue;
340 const size, const alignment = if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) blk: {370 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);
342 defer gpa.free(data);372 defer gpa.free(data);
343 const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;373 const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;
344 break :blk .{ chdr.ch_size, Alignment.fromNonzeroByteUnits(chdr.ch_addralign) };374 break :blk .{ chdr.ch_size, Alignment.fromNonzeroByteUnits(chdr.ch_addralign) };
...@@ -359,7 +389,7 @@ fn initAtoms(...@@ -359,7 +389,7 @@ fn initAtoms(
359 elf.SHT_REL, elf.SHT_RELA => {389 elf.SHT_REL, elf.SHT_RELA => {
360 const atom_index = self.atoms_indexes.items[shdr.sh_info];390 const atom_index = self.atoms_indexes.items[shdr.sh_info];
361 if (self.atom(atom_index)) |atom_ptr| {391 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));
363 defer gpa.free(relocs);393 defer gpa.free(relocs);
364 atom_ptr.relocs_section_index = @intCast(i);394 atom_ptr.relocs_section_index = @intCast(i);
365 const rel_index: u32 = @intCast(self.relocs.items.len);395 const rel_index: u32 = @intCast(self.relocs.items.len);
...@@ -421,7 +451,8 @@ fn initSymbols(...@@ -421,7 +451,8 @@ fn initSymbols(
421fn parseEhFrame(451fn parseEhFrame(
422 self: *Object,452 self: *Object,
423 gpa: Allocator,453 gpa: Allocator,
424 handle: fs.File,454 io: Io,
455 handle: Io.File,
425 shndx: u32,456 shndx: u32,
426 target: *const std.Target,457 target: *const std.Target,
427) !void {458) !void {
...@@ -430,12 +461,12 @@ fn parseEhFrame(...@@ -430,12 +461,12 @@ fn parseEhFrame(
430 else => {},461 else => {},
431 } else null;462 } else null;
432463
433 const raw = try self.preadShdrContentsAlloc(gpa, handle, shndx);464 const raw = try self.preadShdrContentsAlloc(gpa, io, handle, shndx);
434 defer gpa.free(raw);465 defer gpa.free(raw);
435 const data_start: u32 = @intCast(self.eh_frame_data.items.len);466 const data_start: u32 = @intCast(self.eh_frame_data.items.len);
436 try self.eh_frame_data.appendSlice(gpa, raw);467 try self.eh_frame_data.appendSlice(gpa, raw);
437 const relocs = if (relocs_shndx) |index|468 const relocs = if (relocs_shndx) |index|
438 try self.preadRelocsAlloc(gpa, handle, index)469 try self.preadRelocsAlloc(gpa, io, handle, index)
439 else470 else
440 &[0]elf.Elf64_Rela{};471 &[0]elf.Elf64_Rela{};
441 defer gpa.free(relocs);472 defer gpa.free(relocs);
...@@ -1095,13 +1126,18 @@ pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, elf_file: *Elf...@@ -1095,13 +1126,18 @@ pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, elf_file: *Elf
1095}1126}
10961127
1097pub fn updateArSize(self: *Object, elf_file: *Elf) !void {1128pub fn updateArSize(self: *Object, elf_file: *Elf) !void {
1129 const comp = elf_file.base.comp;
1130 const io = comp.io;
1098 self.output_ar_state.size = if (self.archive) |ar| ar.size else size: {1131 self.output_ar_state.size = if (self.archive) |ar| ar.size else size: {
1099 const handle = elf_file.fileHandle(self.file_handle);1132 const handle = elf_file.fileHandle(self.file_handle);
1100 break :size (try handle.stat()).size;1133 break :size (try handle.stat(io)).size;
1101 };1134 };
1102}1135}
11031136
1104pub fn writeAr(self: Object, elf_file: *Elf, writer: anytype) !void {1137pub 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;
1105 const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow;1141 const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow;
1106 const offset: u64 = if (self.archive) |ar| ar.offset else 0;1142 const offset: u64 = if (self.archive) |ar| ar.offset else 0;
1107 const name = fs.path.basename(self.path.sub_path);1143 const name = fs.path.basename(self.path.sub_path);
...@@ -1114,10 +1150,9 @@ pub fn writeAr(self: Object, elf_file: *Elf, writer: anytype) !void {...@@ -1114,10 +1150,9 @@ pub fn writeAr(self: Object, elf_file: *Elf, writer: anytype) !void {
1114 });1150 });
1115 try writer.writeAll(mem.asBytes(&hdr));1151 try writer.writeAll(mem.asBytes(&hdr));
1116 const handle = elf_file.fileHandle(self.file_handle);1152 const handle = elf_file.fileHandle(self.file_handle);
1117 const gpa = elf_file.base.comp.gpa;
1118 const data = try gpa.alloc(u8, size);1153 const data = try gpa.alloc(u8, size);
1119 defer gpa.free(data);1154 defer gpa.free(data);
1120 const amt = try handle.preadAll(data, offset);1155 const amt = try handle.readPositionalAll(io, data, offset);
1121 if (amt != size) return error.InputOutput;1156 if (amt != size) return error.InputOutput;
1122 try writer.writeAll(data);1157 try writer.writeAll(data);
1123}1158}
...@@ -1190,11 +1225,12 @@ pub fn writeSymtab(self: *Object, elf_file: *Elf) void {...@@ -1190,11 +1225,12 @@ pub fn writeSymtab(self: *Object, elf_file: *Elf) void {
1190/// Caller owns the memory.1225/// Caller owns the memory.
1191pub fn codeDecompressAlloc(self: *Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 {1226pub fn codeDecompressAlloc(self: *Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 {
1192 const comp = elf_file.base.comp;1227 const comp = elf_file.base.comp;
1228 const io = comp.io;
1193 const gpa = comp.gpa;1229 const gpa = comp.gpa;
1194 const atom_ptr = self.atom(atom_index).?;1230 const atom_ptr = self.atom(atom_index).?;
1195 const shdr = atom_ptr.inputShdr(elf_file);1231 const shdr = atom_ptr.inputShdr(elf_file);
1196 const handle = elf_file.fileHandle(self.file_handle);1232 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);
1198 defer if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) gpa.free(data);1234 defer if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) gpa.free(data);
11991235
1200 if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) {1236 if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) {
...@@ -1310,18 +1346,18 @@ fn addString(self: *Object, gpa: Allocator, str: []const u8) !u32 {...@@ -1310,18 +1346,18 @@ fn addString(self: *Object, gpa: Allocator, str: []const u8) !u32 {
1310}1346}
13111347
1312/// Caller owns the memory.1348/// 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 {
1314 assert(index < self.shdrs.items.len);1350 assert(index < self.shdrs.items.len);
1315 const offset = if (self.archive) |ar| ar.offset else 0;1351 const offset = if (self.archive) |ar| ar.offset else 0;
1316 const shdr = self.shdrs.items[index];1352 const shdr = self.shdrs.items[index];
1317 const sh_offset = math.cast(u64, shdr.sh_offset) orelse return error.Overflow;1353 const sh_offset = math.cast(u64, shdr.sh_offset) orelse return error.Overflow;
1318 const sh_size = math.cast(u64, shdr.sh_size) orelse return error.Overflow;1354 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);
1320}1356}
13211357
1322/// Caller owns the memory.1358/// Caller owns the memory.
1323fn preadRelocsAlloc(self: Object, gpa: Allocator, handle: fs.File, shndx: u32) ![]align(1) const elf.Elf64_Rela {1359fn preadRelocsAlloc(self: Object, gpa: Allocator, io: Io, handle: Io.File, shndx: u32) ![]align(1) const elf.Elf64_Rela {
1324 const raw = try self.preadShdrContentsAlloc(gpa, handle, shndx);1360 const raw = try self.preadShdrContentsAlloc(gpa, io, handle, shndx);
1325 const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela));1361 const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela));
1326 return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num];1362 return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num];
1327}1363}
...@@ -1552,29 +1588,3 @@ const InArchive = struct {...@@ -1552,29 +1588,3 @@ const InArchive = struct {
1552 offset: u64,1588 offset: u64,
1553 size: u32,1589 size: u32,
1554};1590};
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 @@...@@ -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
1path: Path,18path: Path,
2index: File.Index,19index: File.Index,
320
...@@ -92,16 +109,17 @@ pub const Parsed = struct {...@@ -92,16 +109,17 @@ pub const Parsed = struct {
92109
93pub fn parseHeader(110pub fn parseHeader(
94 gpa: Allocator,111 gpa: Allocator,
112 io: Io,
95 diags: *Diags,113 diags: *Diags,
96 file_path: Path,114 file_path: Path,
97 fs_file: std.fs.File,115 file: Io.File,
98 stat: Stat,116 stat: Stat,
99 target: *const std.Target,117 target: *const std.Target,
100) !Header {118) !Header {
101 var ehdr: elf.Elf64_Ehdr = undefined;119 var ehdr: elf.Elf64_Ehdr = undefined;
102 {120 {
103 const buf = mem.asBytes(&ehdr);121 const buf = mem.asBytes(&ehdr);
104 const amt = try fs_file.preadAll(buf, 0);122 const amt = try file.readPositionalAll(io, buf, 0);
105 if (amt != buf.len) return error.UnexpectedEndOfFile;123 if (amt != buf.len) return error.UnexpectedEndOfFile;
106 }124 }
107 if (!mem.eql(u8, ehdr.e_ident[0..4], "\x7fELF")) return error.BadMagic;125 if (!mem.eql(u8, ehdr.e_ident[0..4], "\x7fELF")) return error.BadMagic;
...@@ -118,7 +136,7 @@ pub fn parseHeader(...@@ -118,7 +136,7 @@ pub fn parseHeader(
118 errdefer gpa.free(sections);136 errdefer gpa.free(sections);
119 {137 {
120 const buf = mem.sliceAsBytes(sections);138 const buf = mem.sliceAsBytes(sections);
121 const amt = try fs_file.preadAll(buf, shoff);139 const amt = try file.readPositionalAll(io, buf, shoff);
122 if (amt != buf.len) return error.UnexpectedEndOfFile;140 if (amt != buf.len) return error.UnexpectedEndOfFile;
123 }141 }
124142
...@@ -143,7 +161,7 @@ pub fn parseHeader(...@@ -143,7 +161,7 @@ pub fn parseHeader(
143 const dynamic_table = try gpa.alloc(elf.Elf64_Dyn, n);161 const dynamic_table = try gpa.alloc(elf.Elf64_Dyn, n);
144 errdefer gpa.free(dynamic_table);162 errdefer gpa.free(dynamic_table);
145 const buf = mem.sliceAsBytes(dynamic_table);163 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);
147 if (amt != buf.len) return error.UnexpectedEndOfFile;165 if (amt != buf.len) return error.UnexpectedEndOfFile;
148 break :dt dynamic_table;166 break :dt dynamic_table;
149 } else &.{};167 } else &.{};
...@@ -158,7 +176,7 @@ pub fn parseHeader(...@@ -158,7 +176,7 @@ pub fn parseHeader(
158 const strtab_shdr = sections[dynsym_shdr.sh_link];176 const strtab_shdr = sections[dynsym_shdr.sh_link];
159 const n = std.math.cast(usize, strtab_shdr.sh_size) orelse return error.Overflow;177 const n = std.math.cast(usize, strtab_shdr.sh_size) orelse return error.Overflow;
160 const buf = try strtab.addManyAsSlice(gpa, n);178 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);
162 if (amt != buf.len) return error.UnexpectedEndOfFile;180 if (amt != buf.len) return error.UnexpectedEndOfFile;
163 }181 }
164182
...@@ -190,9 +208,10 @@ pub fn parseHeader(...@@ -190,9 +208,10 @@ pub fn parseHeader(
190208
191pub fn parse(209pub fn parse(
192 gpa: Allocator,210 gpa: Allocator,
211 io: Io,
193 /// Moves resources from header. Caller may unconditionally deinit.212 /// Moves resources from header. Caller may unconditionally deinit.
194 header: *Header,213 header: *Header,
195 fs_file: std.fs.File,214 file: Io.File,
196) !Parsed {215) !Parsed {
197 const symtab = if (header.dynsym_sect_index) |index| st: {216 const symtab = if (header.dynsym_sect_index) |index| st: {
198 const shdr = header.sections[index];217 const shdr = header.sections[index];
...@@ -200,7 +219,7 @@ pub fn parse(...@@ -200,7 +219,7 @@ pub fn parse(
200 const symtab = try gpa.alloc(elf.Elf64_Sym, n);219 const symtab = try gpa.alloc(elf.Elf64_Sym, n);
201 errdefer gpa.free(symtab);220 errdefer gpa.free(symtab);
202 const buf = mem.sliceAsBytes(symtab);221 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);
204 if (amt != buf.len) return error.UnexpectedEndOfFile;223 if (amt != buf.len) return error.UnexpectedEndOfFile;
205 break :st symtab;224 break :st symtab;
206 } else &.{};225 } else &.{};
...@@ -211,7 +230,7 @@ pub fn parse(...@@ -211,7 +230,7 @@ pub fn parse(
211230
212 if (header.verdef_sect_index) |shndx| {231 if (header.verdef_sect_index) |shndx| {
213 const shdr = header.sections[shndx];232 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);
215 defer gpa.free(verdefs);234 defer gpa.free(verdefs);
216235
217 var offset: u32 = 0;236 var offset: u32 = 0;
...@@ -237,7 +256,7 @@ pub fn parse(...@@ -237,7 +256,7 @@ pub fn parse(
237 const versyms = try gpa.alloc(elf.Versym, symtab.len);256 const versyms = try gpa.alloc(elf.Versym, symtab.len);
238 errdefer gpa.free(versyms);257 errdefer gpa.free(versyms);
239 const buf = mem.sliceAsBytes(versyms);258 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);
241 if (amt != buf.len) return error.UnexpectedEndOfFile;260 if (amt != buf.len) return error.UnexpectedEndOfFile;
242 break :vs versyms;261 break :vs versyms;
243 } else &.{};262 } else &.{};
...@@ -534,19 +553,3 @@ const Format = struct {...@@ -534,19 +553,3 @@ const Format = struct {
534 }553 }
535 }554 }
536};555};
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...@@ -740,7 +740,9 @@ pub fn checkDuplicates(self: *ZigObject, dupes: anytype, elf_file: *Elf) error{O
740/// We need this so that we can write to an archive.740/// We need this so that we can write to an archive.
741/// TODO implement writing ZigObject data directly to a buffer instead.741/// TODO implement writing ZigObject data directly to a buffer instead.
742pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void {742pub 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;
744 const shsize: u64 = switch (elf_file.ptr_width) {746 const shsize: u64 = switch (elf_file.ptr_width) {
745 .p32 => @sizeOf(elf.Elf32_Shdr),747 .p32 => @sizeOf(elf.Elf32_Shdr),
746 .p64 => @sizeOf(elf.Elf64_Shdr),748 .p64 => @sizeOf(elf.Elf64_Shdr),
...@@ -753,7 +755,7 @@ pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void {...@@ -753,7 +755,7 @@ pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void {
753 const size = std.math.cast(usize, end_pos) orelse return error.Overflow;755 const size = std.math.cast(usize, end_pos) orelse return error.Overflow;
754 try self.data.resize(gpa, size);756 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);
757 if (amt != size) return error.InputOutput;759 if (amt != size) return error.InputOutput;
758}760}
759761
...@@ -901,13 +903,15 @@ pub fn writeSymtab(self: ZigObject, elf_file: *Elf) void {...@@ -901,13 +903,15 @@ pub fn writeSymtab(self: ZigObject, elf_file: *Elf) void {
901/// Returns atom's code.903/// Returns atom's code.
902/// Caller owns the memory.904/// Caller owns the memory.
903pub fn codeAlloc(self: *ZigObject, elf_file: *Elf, atom_index: Atom.Index) ![]u8 {905pub 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;
905 const atom_ptr = self.atom(atom_index).?;909 const atom_ptr = self.atom(atom_index).?;
906 const file_offset = atom_ptr.offset(elf_file);910 const file_offset = atom_ptr.offset(elf_file);
907 const size = std.math.cast(usize, atom_ptr.size) orelse return error.Overflow;911 const size = std.math.cast(usize, atom_ptr.size) orelse return error.Overflow;
908 const code = try gpa.alloc(u8, size);912 const code = try gpa.alloc(u8, size);
909 errdefer gpa.free(code);913 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);
911 if (amt != code.len) {915 if (amt != code.len) {
912 log.err("fetching code for {s} failed", .{atom_ptr.name(elf_file)});916 log.err("fetching code for {s} failed", .{atom_ptr.name(elf_file)});
913 return error.InputOutput;917 return error.InputOutput;
...@@ -1365,6 +1369,8 @@ fn updateNavCode(...@@ -1365,6 +1369,8 @@ fn updateNavCode(
1365) link.File.UpdateNavError!void {1369) link.File.UpdateNavError!void {
1366 const zcu = pt.zcu;1370 const zcu = pt.zcu;
1367 const gpa = zcu.gpa;1371 const gpa = zcu.gpa;
1372 const comp = elf_file.base.comp;
1373 const io = comp.io;
1368 const ip = &zcu.intern_pool;1374 const ip = &zcu.intern_pool;
1369 const nav = ip.getNav(nav_index);1375 const nav = ip.getNav(nav_index);
13701376
...@@ -1449,8 +1455,8 @@ fn updateNavCode(...@@ -1449,8 +1455,8 @@ fn updateNavCode(
1449 const shdr = elf_file.sections.items(.shdr)[shdr_index];1455 const shdr = elf_file.sections.items(.shdr)[shdr_index];
1450 if (shdr.sh_type != elf.SHT_NOBITS) {1456 if (shdr.sh_type != elf.SHT_NOBITS) {
1451 const file_offset = atom_ptr.offset(elf_file);1457 const file_offset = atom_ptr.offset(elf_file);
1452 elf_file.base.file.?.pwriteAll(code, file_offset) catch |err|1458 elf_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err|
1453 return elf_file.base.cgFail(nav_index, "failed to write to output file: {s}", .{@errorName(err)});1459 return elf_file.base.cgFail(nav_index, "failed to write to output file: {t}", .{err});
1454 log.debug("writing {f} from 0x{x} to 0x{x}", .{ nav.fqn.fmt(ip), file_offset, file_offset + code.len });1460 log.debug("writing {f} from 0x{x} to 0x{x}", .{ nav.fqn.fmt(ip), file_offset, file_offset + code.len });
1455 }1461 }
1456}1462}
...@@ -1467,6 +1473,8 @@ fn updateTlv(...@@ -1467,6 +1473,8 @@ fn updateTlv(
1467 const zcu = pt.zcu;1473 const zcu = pt.zcu;
1468 const ip = &zcu.intern_pool;1474 const ip = &zcu.intern_pool;
1469 const gpa = zcu.gpa;1475 const gpa = zcu.gpa;
1476 const comp = elf_file.base.comp;
1477 const io = comp.io;
1470 const nav = ip.getNav(nav_index);1478 const nav = ip.getNav(nav_index);
14711479
1472 log.debug("updateTlv {f}({d})", .{ nav.fqn.fmt(ip), nav_index });1480 log.debug("updateTlv {f}({d})", .{ nav.fqn.fmt(ip), nav_index });
...@@ -1503,8 +1511,8 @@ fn updateTlv(...@@ -1503,8 +1511,8 @@ fn updateTlv(
1503 const shdr = elf_file.sections.items(.shdr)[shndx];1511 const shdr = elf_file.sections.items(.shdr)[shndx];
1504 if (shdr.sh_type != elf.SHT_NOBITS) {1512 if (shdr.sh_type != elf.SHT_NOBITS) {
1505 const file_offset = atom_ptr.offset(elf_file);1513 const file_offset = atom_ptr.offset(elf_file);
1506 elf_file.base.file.?.pwriteAll(code, file_offset) catch |err|1514 elf_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err|
1507 return elf_file.base.cgFail(nav_index, "failed to write to output file: {s}", .{@errorName(err)});1515 return elf_file.base.cgFail(nav_index, "failed to write to output file: {t}", .{err});
1508 log.debug("writing TLV {s} from 0x{x} to 0x{x}", .{1516 log.debug("writing TLV {s} from 0x{x} to 0x{x}", .{
1509 atom_ptr.name(elf_file),1517 atom_ptr.name(elf_file),
1510 file_offset,1518 file_offset,
...@@ -2003,6 +2011,8 @@ fn trampolineSize(cpu_arch: std.Target.Cpu.Arch) u64 {...@@ -2003,6 +2011,8 @@ fn trampolineSize(cpu_arch: std.Target.Cpu.Arch) u64 {
2003}2011}
20042012
2005fn writeTrampoline(tr_sym: Symbol, target: Symbol, elf_file: *Elf) !void {2013fn writeTrampoline(tr_sym: Symbol, target: Symbol, elf_file: *Elf) !void {
2014 const comp = elf_file.base.comp;
2015 const io = comp.io;
2006 const atom_ptr = tr_sym.atom(elf_file).?;2016 const atom_ptr = tr_sym.atom(elf_file).?;
2007 const fileoff = atom_ptr.offset(elf_file);2017 const fileoff = atom_ptr.offset(elf_file);
2008 const source_addr = tr_sym.address(.{}, elf_file);2018 const source_addr = tr_sym.address(.{}, elf_file);
...@@ -2012,7 +2022,7 @@ fn writeTrampoline(tr_sym: Symbol, target: Symbol, elf_file: *Elf) !void {...@@ -2012,7 +2022,7 @@ fn writeTrampoline(tr_sym: Symbol, target: Symbol, elf_file: *Elf) !void {
2012 .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf),2022 .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf),
2013 else => @panic("TODO implement write trampoline for this CPU arch"),2023 else => @panic("TODO implement write trampoline for this CPU arch"),
2014 };2024 };
2015 try elf_file.base.file.?.pwriteAll(out, fileoff);2025 try elf_file.base.file.?.writePositionalAll(io, out, fileoff);
20162026
2017 if (elf_file.base.child_pid) |pid| {2027 if (elf_file.base.child_pid) |pid| {
2018 switch (builtin.os.tag) {2028 switch (builtin.os.tag) {
src/link/Elf/file.zig+18-17
...@@ -1,3 +1,20 @@...@@ -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
1pub const File = union(enum) {18pub const File = union(enum) {
2 zig_object: *ZigObject,19 zig_object: *ZigObject,
3 linker_defined: *LinkerDefined,20 linker_defined: *LinkerDefined,
...@@ -279,22 +296,6 @@ pub const File = union(enum) {...@@ -279,22 +296,6 @@ pub const File = union(enum) {
279 shared_object: SharedObject,296 shared_object: SharedObject,
280 };297 };
281298
282 pub const Handle = std.fs.File;299 pub const Handle = Io.File;
283 pub const HandleIndex = Index;300 pub const HandleIndex = Index;
284};301};
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 @@...@@ -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
1pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void {21pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void {
2 const gpa = comp.gpa;22 const gpa = comp.gpa;
23 const io = comp.io;
3 const diags = &comp.link_diags;24 const diags = &comp.link_diags;
425
5 if (diags.hasErrors()) return error.LinkFailure;26 if (diags.hasErrors()) return error.LinkFailure;
...@@ -125,8 +146,8 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void {...@@ -125,8 +146,8 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void {
125146
126 assert(writer.buffered().len == total_size);147 assert(writer.buffered().len == total_size);
127148
128 try elf_file.base.file.?.setEndPos(total_size);149 try elf_file.base.file.?.setLength(io, total_size);
129 try elf_file.base.file.?.pwriteAll(writer.buffered(), 0);150 try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), 0);
130151
131 if (diags.hasErrors()) return error.LinkFailure;152 if (diags.hasErrors()) return error.LinkFailure;
132}153}
...@@ -330,13 +351,7 @@ fn allocateAllocSections(elf_file: *Elf) !void {...@@ -330,13 +351,7 @@ fn allocateAllocSections(elf_file: *Elf) !void {
330351
331 if (shdr.sh_offset > 0) {352 if (shdr.sh_offset > 0) {
332 const existing_size = elf_file.sectionSize(@intCast(shndx));353 const existing_size = elf_file.sectionSize(@intCast(shndx));
333 const amt = try elf_file.base.file.?.copyRangeAll(354 try elf_file.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size);
334 shdr.sh_offset,
335 elf_file.base.file.?,
336 new_offset,
337 existing_size,
338 );
339 if (amt != existing_size) return error.InputOutput;
340 }355 }
341356
342 shdr.sh_offset = new_offset;357 shdr.sh_offset = new_offset;
...@@ -360,7 +375,9 @@ fn writeAtoms(elf_file: *Elf) !void {...@@ -360,7 +375,9 @@ fn writeAtoms(elf_file: *Elf) !void {
360}375}
361376
362fn writeSyntheticSections(elf_file: *Elf) !void {377fn 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;
364 const slice = elf_file.sections.slice();381 const slice = elf_file.sections.slice();
365382
366 const SortRelocs = struct {383 const SortRelocs = struct {
...@@ -397,7 +414,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void {...@@ -397,7 +414,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void {
397 shdr.sh_offset + shdr.sh_size,414 shdr.sh_offset + shdr.sh_size,
398 });415 });
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);
401 }418 }
402419
403 if (elf_file.section_indexes.eh_frame) |shndx| {420 if (elf_file.section_indexes.eh_frame) |shndx| {
...@@ -417,7 +434,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void {...@@ -417,7 +434,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void {
417 shdr.sh_offset + sh_size,434 shdr.sh_offset + sh_size,
418 });435 });
419 assert(writer.buffered().len == sh_size - existing_size);436 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);
421 }438 }
422 if (elf_file.section_indexes.eh_frame_rela) |shndx| {439 if (elf_file.section_indexes.eh_frame_rela) |shndx| {
423 const shdr = slice.items(.shdr)[shndx];440 const shdr = slice.items(.shdr)[shndx];
...@@ -435,7 +452,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void {...@@ -435,7 +452,7 @@ fn writeSyntheticSections(elf_file: *Elf) !void {
435 shdr.sh_offset,452 shdr.sh_offset,
436 shdr.sh_offset + shdr.sh_size,453 shdr.sh_offset + shdr.sh_size,
437 });454 });
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);
439 }456 }
440457
441 try writeGroups(elf_file);458 try writeGroups(elf_file);
...@@ -444,7 +461,9 @@ fn writeSyntheticSections(elf_file: *Elf) !void {...@@ -444,7 +461,9 @@ fn writeSyntheticSections(elf_file: *Elf) !void {
444}461}
445462
446fn writeGroups(elf_file: *Elf) !void {463fn 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;
448 for (elf_file.group_sections.items) |cgs| {467 for (elf_file.group_sections.items) |cgs| {
449 const shdr = elf_file.sections.items(.shdr)[cgs.shndx];468 const shdr = elf_file.sections.items(.shdr)[cgs.shndx];
450 const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow;469 const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow;
...@@ -457,25 +476,6 @@ fn writeGroups(elf_file: *Elf) !void {...@@ -457,25 +476,6 @@ fn writeGroups(elf_file: *Elf) !void {
457 shdr.sh_offset,476 shdr.sh_offset,
458 shdr.sh_offset + shdr.sh_size,477 shdr.sh_offset + shdr.sh_size,
459 });478 });
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);
461 }480 }
462}481}
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 @@...@@ -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
1base: link.File,21base: link.File,
2options: link.File.OpenOptions,22options: link.File.OpenOptions,
3mf: MappedFile,23mf: MappedFile,
...@@ -908,6 +928,7 @@ fn create(...@@ -908,6 +928,7 @@ fn create(
908 path: std.Build.Cache.Path,928 path: std.Build.Cache.Path,
909 options: link.File.OpenOptions,929 options: link.File.OpenOptions,
910) !*Elf {930) !*Elf {
931 const io = comp.io;
911 const target = &comp.root_mod.resolved_target.result;932 const target = &comp.root_mod.resolved_target.result;
912 assert(target.ofmt == .elf);933 assert(target.ofmt == .elf);
913 const class: std.elf.CLASS = switch (target.ptrBitWidth()) {934 const class: std.elf.CLASS = switch (target.ptrBitWidth()) {
...@@ -953,11 +974,11 @@ fn create(...@@ -953,11 +974,11 @@ fn create(
953 };974 };
954975
955 const elf = try arena.create(Elf);976 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, .{
957 .read = true,978 .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),
959 });980 });
960 errdefer file.close(comp.io);981 errdefer file.close(io);
961 elf.* = .{982 elf.* = .{
962 .base = .{983 .base = .{
963 .tag = .elf2,984 .tag = .elf2,
...@@ -965,7 +986,7 @@ fn create(...@@ -965,7 +986,7 @@ fn create(
965 .comp = comp,986 .comp = comp,
966 .emit = path,987 .emit = path,
967988
968 .file = .adaptFromNewApi(file),989 .file = file,
969 .gc_sections = false,990 .gc_sections = false,
970 .print_gc_sections = false,991 .print_gc_sections = false,
971 .build_id = .none,992 .build_id = .none,
...@@ -973,7 +994,7 @@ fn create(...@@ -973,7 +994,7 @@ fn create(
973 .stack_size = 0,994 .stack_size = 0,
974 },995 },
975 .options = options,996 .options = options,
976 .mf = try .init(file, comp.gpa),997 .mf = try .init(file, comp.gpa, io),
977 .ni = .{998 .ni = .{
978 .tls = .none,999 .tls = .none,
979 },1000 },
...@@ -1973,8 +1994,8 @@ pub fn lazySymbol(elf: *Elf, lazy: link.File.LazySymbol) !Symbol.Index {...@@ -1973,8 +1994,8 @@ pub fn lazySymbol(elf: *Elf, lazy: link.File.LazySymbol) !Symbol.Index {
1973 return lazy_gop.value_ptr.*;1994 return lazy_gop.value_ptr.*;
1974}1995}
19751996
1976pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError ||1997pub fn loadInput(elf: *Elf, input: link.Input) (Io.File.Reader.SizeError ||
1977 std.Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, BadMagic, LinkFailure })!void {1998 Io.File.Reader.Error || MappedFile.Error || error{ EndOfStream, BadMagic, LinkFailure })!void {
1978 const io = elf.base.comp.io;1999 const io = elf.base.comp.io;
1979 var buf: [4096]u8 = undefined;2000 var buf: [4096]u8 = undefined;
1980 switch (input) {2001 switch (input) {
...@@ -2007,7 +2028,7 @@ pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError ||...@@ -2007,7 +2028,7 @@ pub fn loadInput(elf: *Elf, input: link.Input) (std.fs.File.Reader.SizeError ||
2007 .dso_exact => |dso_exact| try elf.loadDsoExact(dso_exact.name),2028 .dso_exact => |dso_exact| try elf.loadDsoExact(dso_exact.name),
2008 }2029 }
2009}2030}
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 {
2011 const comp = elf.base.comp;2032 const comp = elf.base.comp;
2012 const gpa = comp.gpa;2033 const gpa = comp.gpa;
2013 const diags = &comp.link_diags;2034 const diags = &comp.link_diags;
...@@ -2067,7 +2088,7 @@ fn loadObject(...@@ -2067,7 +2088,7 @@ fn loadObject(
2067 elf: *Elf,2088 elf: *Elf,
2068 path: std.Build.Cache.Path,2089 path: std.Build.Cache.Path,
2069 member: ?[]const u8,2090 member: ?[]const u8,
2070 fr: *std.Io.File.Reader,2091 fr: *Io.File.Reader,
2071 fl: MappedFile.Node.FileLocation,2092 fl: MappedFile.Node.FileLocation,
2072) !void {2093) !void {
2073 const comp = elf.base.comp;2094 const comp = elf.base.comp;
...@@ -2310,7 +2331,7 @@ fn loadObject(...@@ -2310,7 +2331,7 @@ fn loadObject(
2310 },2331 },
2311 }2332 }
2312}2333}
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 {
2314 const comp = elf.base.comp;2335 const comp = elf.base.comp;
2315 const diags = &comp.link_diags;2336 const diags = &comp.link_diags;
2316 const r = &fr.interface;2337 const r = &fr.interface;
...@@ -3305,12 +3326,13 @@ fn flushInputSection(elf: *Elf, isi: Node.InputSectionIndex) !void {...@@ -3305,12 +3326,13 @@ fn flushInputSection(elf: *Elf, isi: Node.InputSectionIndex) !void {
3305 const file_loc = isi.fileLocation(elf);3326 const file_loc = isi.fileLocation(elf);
3306 if (file_loc.size == 0) return;3327 if (file_loc.size == 0) return;
3307 const comp = elf.base.comp;3328 const comp = elf.base.comp;
3329 const io = comp.io;
3308 const gpa = comp.gpa;3330 const gpa = comp.gpa;
3309 const ii = isi.input(elf);3331 const ii = isi.input(elf);
3310 const path = ii.path(elf);3332 const path = ii.path(elf);
3311 const file = try path.root_dir.handle.adaptToNewApi().openFile(comp.io, path.sub_path, .{});3333 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
3312 defer file.close(comp.io);3334 defer file.close(io);
3313 var fr = file.reader(comp.io, &.{});3335 var fr = file.reader(io, &.{});
3314 try fr.seekTo(file_loc.offset);3336 try fr.seekTo(file_loc.offset);
3315 var nw: MappedFile.Node.Writer = undefined;3337 var nw: MappedFile.Node.Writer = undefined;
3316 const si = isi.symbol(elf);3338 const si = isi.symbol(elf);
...@@ -3707,10 +3729,16 @@ pub fn deleteExport(elf: *Elf, exported: Zcu.Exported, name: InternPool.NullTerm...@@ -3707,10 +3729,16 @@ pub fn deleteExport(elf: *Elf, exported: Zcu.Exported, name: InternPool.NullTerm
3707 _ = name;3729 _ = name;
3708}3730}
37093731
3710pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) void {3732pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) Io.Cancelable!void {
3711 const w, _ = std.debug.lockStderrWriter(&.{});3733 const comp = elf.base.comp;
3712 defer std.debug.unlockStderrWriter();3734 const io = comp.io;
3713 elf.printNode(tid, w, .root, 0) catch {};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 };
3714}3742}
37153743
3716pub fn printNode(3744pub fn printNode(
...@@ -3822,19 +3850,3 @@ pub fn printNode(...@@ -3822,19 +3850,3 @@ pub fn printNode(
3822 try w.writeByte('\n');3850 try w.writeByte('\n');
3823 }3851 }
3824}3852}
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 {...@@ -359,6 +359,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void {
359fn coffLink(lld: *Lld, arena: Allocator) !void {359fn coffLink(lld: *Lld, arena: Allocator) !void {
360 const comp = lld.base.comp;360 const comp = lld.base.comp;
361 const gpa = comp.gpa;361 const gpa = comp.gpa;
362 const io = comp.io;
362 const base = &lld.base;363 const base = &lld.base;
363 const coff = &lld.ofmt.coff;364 const coff = &lld.ofmt.coff;
364365
...@@ -400,11 +401,12 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {...@@ -400,11 +401,12 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
400 // regarding eliding redundant object -> object transformations.401 // regarding eliding redundant object -> object transformations.
401 return error.NoObjectsToLink;402 return error.NoObjectsToLink;
402 };403 };
403 try std.fs.Dir.copyFile(404 try Io.Dir.copyFile(
404 the_object_path.root_dir.handle,405 the_object_path.root_dir.handle,
405 the_object_path.sub_path,406 the_object_path.sub_path,
406 directory.handle,407 directory.handle,
407 base.emit.sub_path,408 base.emit.sub_path,
409 io,
408 .{},410 .{},
409 );411 );
410 } else {412 } else {
...@@ -718,13 +720,13 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {...@@ -718,13 +720,13 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
718 argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena));720 argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena));
719 continue;721 continue;
720 }722 }
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| {
722 argv.appendAssumeCapacity(full_path);724 argv.appendAssumeCapacity(full_path);
723 continue;725 continue;
724 }726 }
725 if (target.abi.isGnu()) {727 if (target.abi.isGnu()) {
726 const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key});728 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| {
728 argv.appendAssumeCapacity(full_path);730 argv.appendAssumeCapacity(full_path);
729 continue;731 continue;
730 }732 }
...@@ -741,9 +743,9 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {...@@ -741,9 +743,9 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
741 try spawnLld(comp, arena, argv.items);743 try spawnLld(comp, arena, argv.items);
742 }744 }
743}745}
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 {
745 for (lib_directories) |lib_directory| {747 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) {
747 error.FileNotFound => continue,749 error.FileNotFound => continue,
748 else => |e| return e,750 else => |e| return e,
749 };751 };
...@@ -755,6 +757,7 @@ fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Cache.Di...@@ -755,6 +757,7 @@ fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Cache.Di
755fn elfLink(lld: *Lld, arena: Allocator) !void {757fn elfLink(lld: *Lld, arena: Allocator) !void {
756 const comp = lld.base.comp;758 const comp = lld.base.comp;
757 const gpa = comp.gpa;759 const gpa = comp.gpa;
760 const io = comp.io;
758 const diags = &comp.link_diags;761 const diags = &comp.link_diags;
759 const base = &lld.base;762 const base = &lld.base;
760 const elf = &lld.ofmt.elf;763 const elf = &lld.ofmt.elf;
...@@ -816,11 +819,12 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {...@@ -816,11 +819,12 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
816 // regarding eliding redundant object -> object transformations.819 // regarding eliding redundant object -> object transformations.
817 return error.NoObjectsToLink;820 return error.NoObjectsToLink;
818 };821 };
819 try std.fs.Dir.copyFile(822 try Io.Dir.copyFile(
820 the_object_path.root_dir.handle,823 the_object_path.root_dir.handle,
821 the_object_path.sub_path,824 the_object_path.sub_path,
822 directory.handle,825 directory.handle,
823 base.emit.sub_path,826 base.emit.sub_path,
827 io,
824 .{},828 .{},
825 );829 );
826 } else {830 } else {
...@@ -1326,6 +1330,7 @@ fn getLDMOption(target: *const std.Target) ?[]const u8 {...@@ -1326,6 +1330,7 @@ fn getLDMOption(target: *const std.Target) ?[]const u8 {
1326}1330}
1327fn wasmLink(lld: *Lld, arena: Allocator) !void {1331fn wasmLink(lld: *Lld, arena: Allocator) !void {
1328 const comp = lld.base.comp;1332 const comp = lld.base.comp;
1333 const diags = &comp.link_diags;
1329 const shared_memory = comp.config.shared_memory;1334 const shared_memory = comp.config.shared_memory;
1330 const export_memory = comp.config.export_memory;1335 const export_memory = comp.config.export_memory;
1331 const import_memory = comp.config.import_memory;1336 const import_memory = comp.config.import_memory;
...@@ -1334,6 +1339,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {...@@ -1334,6 +1339,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
1334 const wasm = &lld.ofmt.wasm;1339 const wasm = &lld.ofmt.wasm;
13351340
1336 const gpa = comp.gpa;1341 const gpa = comp.gpa;
1342 const io = comp.io;
13371343
1338 const directory = base.emit.root_dir; // Just an alias to make it shorter to type.1344 const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
1339 const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});1345 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 {...@@ -1371,11 +1377,12 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
1371 // regarding eliding redundant object -> object transformations.1377 // regarding eliding redundant object -> object transformations.
1372 return error.NoObjectsToLink;1378 return error.NoObjectsToLink;
1373 };1379 };
1374 try fs.Dir.copyFile(1380 try Io.Dir.copyFile(
1375 the_object_path.root_dir.handle,1381 the_object_path.root_dir.handle,
1376 the_object_path.sub_path,1382 the_object_path.sub_path,
1377 directory.handle,1383 directory.handle,
1378 base.emit.sub_path,1384 base.emit.sub_path,
1385 io,
1379 .{},1386 .{},
1380 );1387 );
1381 } else {1388 } else {
...@@ -1565,27 +1572,23 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {...@@ -1565,27 +1572,23 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
1565 // is not the case, it means we will get "exec format error" when trying to run1572 // is not the case, it means we will get "exec format error" when trying to run
1566 // it, and then can react to that in the same way as trying to run an ELF file1573 // it, and then can react to that in the same way as trying to run an ELF file
1567 // from a foreign CPU architecture.1574 // from a foreign CPU architecture.
1568 if (fs.has_executable_bit and target.os.tag == .wasi and1575 if (Io.File.Permissions.has_executable_bit and target.os.tag == .wasi and
1569 comp.config.output_mode == .Exe)1576 comp.config.output_mode == .Exe)
1570 {1577 {
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.
1574 // chmod does not interact with umask, so we use a conservative -rwxr--r-- here.1578 // 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) {1579 Io.Dir.cwd().setFilePermissions(io, full_out_path, .fromMode(0o744), .{}) catch |err|
1576 error.OperationNotSupported => unreachable, // Not a symlink.1580 return diags.fail("{s}: failed to enable executable permissions: {t}", .{ full_out_path, err });
1577 else => |e| return e,
1578 };
1579 }1581 }
1580 }1582 }
1581}1583}
15821584
1583fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !void {1585fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !void {
1584 const io = comp.io;1586 const io = comp.io;
1587 const gpa = comp.gpa;
15851588
1586 if (comp.verbose_link) {1589 if (comp.verbose_link) {
1587 // Skip over our own name so that the LLD linker name is the first argv item.1590 // 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..]);
1589 }1592 }
15901593
1591 // If possible, we run LLD as a child process because it does not always1594 // 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...@@ -1599,7 +1602,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
1599 }1602 }
16001603
1601 var stderr: []u8 = &.{};1604 var stderr: []u8 = &.{};
1602 defer comp.gpa.free(stderr);1605 defer gpa.free(stderr);
16031606
1604 var child = std.process.Child.init(argv, arena);1607 var child = std.process.Child.init(argv, arena);
1605 const term = (if (comp.clang_passthrough_mode) term: {1608 const term = (if (comp.clang_passthrough_mode) term: {
...@@ -1607,16 +1610,16 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi...@@ -1607,16 +1610,16 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
1607 child.stdout_behavior = .Inherit;1610 child.stdout_behavior = .Inherit;
1608 child.stderr_behavior = .Inherit;1611 child.stderr_behavior = .Inherit;
16091612
1610 break :term child.spawnAndWait();1613 break :term child.spawnAndWait(io);
1611 } else term: {1614 } else term: {
1612 child.stdin_behavior = .Ignore;1615 child.stdin_behavior = .Ignore;
1613 child.stdout_behavior = .Ignore;1616 child.stdout_behavior = .Ignore;
1614 child.stderr_behavior = .Pipe;1617 child.stderr_behavior = .Pipe;
16151618
1616 child.spawn() catch |err| break :term err;1619 child.spawn(io) catch |err| break :term err;
1617 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});1620 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
1618 stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited);1621 stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited);
1619 break :term child.wait();1622 break :term child.wait(io);
1620 }) catch |first_err| term: {1623 }) catch |first_err| term: {
1621 const err = switch (first_err) {1624 const err = switch (first_err) {
1622 error.NameTooLong => err: {1625 error.NameTooLong => err: {
...@@ -1624,13 +1627,13 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi...@@ -1624,13 +1627,13 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
1624 const rand_int = std.crypto.random.int(u64);1627 const rand_int = std.crypto.random.int(u64);
1625 const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp";1628 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, .{});1630 const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{});
1628 defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err|1631 defer comp.dirs.local_cache.handle.deleteFile(io, rsp_path) catch |err|
1629 log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) });1632 log.warn("failed to delete response file {s}: {t}", .{ rsp_path, err });
1630 {1633 {
1631 defer rsp_file.close();1634 defer rsp_file.close(io);
1632 var rsp_file_buffer: [1024]u8 = undefined;1635 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);
1634 const rsp_writer = &rsp_file_writer.interface;1637 const rsp_writer = &rsp_file_writer.interface;
1635 for (argv[2..]) |arg| {1638 for (argv[2..]) |arg| {
1636 try rsp_writer.writeByte('"');1639 try rsp_writer.writeByte('"');
...@@ -1657,16 +1660,16 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi...@@ -1657,16 +1660,16 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
1657 rsp_child.stdout_behavior = .Inherit;1660 rsp_child.stdout_behavior = .Inherit;
1658 rsp_child.stderr_behavior = .Inherit;1661 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;
1661 } else {1664 } else {
1662 rsp_child.stdin_behavior = .Ignore;1665 rsp_child.stdin_behavior = .Ignore;
1663 rsp_child.stdout_behavior = .Ignore;1666 rsp_child.stdout_behavior = .Ignore;
1664 rsp_child.stderr_behavior = .Pipe;1667 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;
1667 var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{});1670 var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{});
1668 stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited);1671 stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited);
1669 break :term rsp_child.wait() catch |err| break :err err;1672 break :term rsp_child.wait(io) catch |err| break :err err;
1670 }1673 }
1671 },1674 },
1672 else => first_err,1675 else => first_err,
...@@ -1692,6 +1695,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi...@@ -1692,6 +1695,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
1692}1695}
16931696
1694const std = @import("std");1697const std = @import("std");
1698const Io = std.Io;
1695const Allocator = std.mem.Allocator;1699const Allocator = std.mem.Allocator;
1696const Cache = std.Build.Cache;1700const Cache = std.Build.Cache;
1697const allocPrint = std.fmt.allocPrint;1701const allocPrint = std.fmt.allocPrint;
src/link/MachO.zig+146-89
...@@ -219,10 +219,12 @@ pub fn createEmpty(...@@ -219,10 +219,12 @@ pub fn createEmpty(
219 };219 };
220 errdefer self.base.destroy();220 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, .{
223 .truncate = true,225 .truncate = true,
224 .read = true,226 .read = true,
225 .mode = link.File.determineMode(output_mode, link_mode),227 .permissions = link.File.determinePermissions(output_mode, link_mode),
226 });228 });
227229
228 // Append null file230 // Append null file
...@@ -267,14 +269,16 @@ pub fn open(...@@ -267,14 +269,16 @@ pub fn open(
267}269}
268270
269pub fn deinit(self: *MachO) void {271pub 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
272 if (self.d_sym) |*d_sym| {276 if (self.d_sym) |*d_sym| {
273 d_sym.deinit();277 d_sym.deinit();
274 }278 }
275279
276 for (self.file_handles.items) |handle| {280 for (self.file_handles.items) |handle| {
277 handle.close();281 handle.close(io);
278 }282 }
279 self.file_handles.deinit(gpa);283 self.file_handles.deinit(gpa);
280284
...@@ -343,7 +347,8 @@ pub fn flush(...@@ -343,7 +347,8 @@ pub fn flush(
343347
344 const comp = self.base.comp;348 const comp = self.base.comp;
345 const gpa = comp.gpa;349 const gpa = comp.gpa;
346 const diags = &self.base.comp.link_diags;350 const io = comp.io;
351 const diags = &comp.link_diags;
347352
348 const sub_prog_node = prog_node.start("MachO Flush", 0);353 const sub_prog_node = prog_node.start("MachO Flush", 0);
349 defer sub_prog_node.end();354 defer sub_prog_node.end();
...@@ -376,26 +381,26 @@ pub fn flush(...@@ -376,26 +381,26 @@ pub fn flush(
376 // in this set.381 // in this set.
377 try positionals.ensureUnusedCapacity(comp.c_object_table.keys().len);382 try positionals.ensureUnusedCapacity(comp.c_object_table.keys().len);
378 for (comp.c_object_table.keys()) |key| {383 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));
380 }385 }
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
384 if (comp.config.any_sanitize_thread) {389 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));
386 }391 }
387392
388 if (comp.config.any_fuzz) {393 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));
390 }395 }
391396
392 if (comp.ubsan_rt_lib) |crt_file| {397 if (comp.ubsan_rt_lib) |crt_file| {
393 const path = crt_file.full_object_path;398 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|
395 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});400 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
396 } else if (comp.ubsan_rt_obj) |crt_file| {401 } else if (comp.ubsan_rt_obj) |crt_file| {
397 const path = crt_file.full_object_path;402 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|
399 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});404 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
400 }405 }
401406
...@@ -430,7 +435,7 @@ pub fn flush(...@@ -430,7 +435,7 @@ pub fn flush(
430 if (comp.config.link_libc and is_exe_or_dyn_lib) {435 if (comp.config.link_libc and is_exe_or_dyn_lib) {
431 if (comp.zigc_static_lib) |zigc| {436 if (comp.zigc_static_lib) |zigc| {
432 const path = zigc.full_object_path;437 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|
434 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});439 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
435 }440 }
436 }441 }
...@@ -453,12 +458,12 @@ pub fn flush(...@@ -453,12 +458,12 @@ pub fn flush(
453 for (system_libs.items) |lib| {458 for (system_libs.items) |lib| {
454 switch (Compilation.classifyFileExt(lib.path.sub_path)) {459 switch (Compilation.classifyFileExt(lib.path.sub_path)) {
455 .shared_library => {460 .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);
457 self.classifyInputFile(dso_input) catch |err|462 self.classifyInputFile(dso_input) catch |err|
458 diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});463 diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});
459 },464 },
460 .static_library => {465 .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);
462 self.classifyInputFile(archive_input) catch |err|467 self.classifyInputFile(archive_input) catch |err|
463 diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});468 diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});
464 },469 },
...@@ -469,11 +474,11 @@ pub fn flush(...@@ -469,11 +474,11 @@ pub fn flush(
469 // Finally, link against compiler_rt.474 // Finally, link against compiler_rt.
470 if (comp.compiler_rt_lib) |crt_file| {475 if (comp.compiler_rt_lib) |crt_file| {
471 const path = crt_file.full_object_path;476 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|
473 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});478 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
474 } else if (comp.compiler_rt_obj) |crt_file| {479 } else if (comp.compiler_rt_obj) |crt_file| {
475 const path = crt_file.full_object_path;480 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|
477 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});482 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
478 }483 }
479484
...@@ -564,7 +569,7 @@ pub fn flush(...@@ -564,7 +569,7 @@ pub fn flush(
564 self.writeLinkeditSectionsToFile() catch |err| switch (err) {569 self.writeLinkeditSectionsToFile() catch |err| switch (err) {
565 error.OutOfMemory => return error.OutOfMemory,570 error.OutOfMemory => return error.OutOfMemory,
566 error.LinkFailure => return error.LinkFailure,571 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}),
568 };573 };
569574
570 var codesig: ?CodeSignature = if (self.requiresCodeSig()) blk: {575 var codesig: ?CodeSignature = if (self.requiresCodeSig()) blk: {
...@@ -575,8 +580,8 @@ pub fn flush(...@@ -575,8 +580,8 @@ pub fn flush(
575 // where the code signature goes into.580 // where the code signature goes into.
576 var codesig = CodeSignature.init(self.getPageSize());581 var codesig = CodeSignature.init(self.getPageSize());
577 codesig.code_directory.ident = fs.path.basename(self.base.emit.sub_path);582 codesig.code_directory.ident = fs.path.basename(self.base.emit.sub_path);
578 if (self.entitlements) |path| codesig.addEntitlements(gpa, path) catch |err|583 if (self.entitlements) |path| codesig.addEntitlements(gpa, io, path) catch |err|
579 return diags.fail("failed to add entitlements from {s}: {s}", .{ path, @errorName(err) });584 return diags.fail("failed to add entitlements from {s}: {t}", .{ path, err });
580 try self.writeCodeSignaturePadding(&codesig);585 try self.writeCodeSignaturePadding(&codesig);
581 break :blk codesig;586 break :blk codesig;
582 } else null;587 } else null;
...@@ -612,15 +617,17 @@ pub fn flush(...@@ -612,15 +617,17 @@ pub fn flush(
612 else => |e| return diags.fail("failed to write code signature: {s}", .{@errorName(e)}),617 else => |e| return diags.fail("failed to write code signature: {s}", .{@errorName(e)}),
613 };618 };
614 const emit = self.base.emit;619 const emit = self.base.emit;
615 invalidateKernelCache(emit.root_dir.handle, emit.sub_path) catch |err| switch (err) {620 invalidateKernelCache(io, 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)}),621 else => |e| return diags.fail("failed to invalidate kernel cache: {t}", .{e}),
617 };622 };
618 }623 }
619}624}
620625
621/// --verbose-link output626/// --verbose-link output
622fn dumpArgv(self: *MachO, comp: *Compilation) !void {627fn dumpArgv(self: *MachO, comp: *Compilation) !void {
623 const gpa = self.base.comp.gpa;628 const gpa = comp.gpa;
629 const io = comp.io;
630
624 var arena_allocator = std.heap.ArenaAllocator.init(gpa);631 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
625 defer arena_allocator.deinit();632 defer arena_allocator.deinit();
626 const arena = arena_allocator.allocator();633 const arena = arena_allocator.allocator();
...@@ -815,7 +822,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {...@@ -815,7 +822,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
815 if (comp.ubsan_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena));822 if (comp.ubsan_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena));
816 }823 }
817824
818 Compilation.dump_argv(argv.items);825 try Compilation.dumpArgv(io, argv.items);
819}826}
820827
821/// TODO delete this, libsystem must be resolved when setting up the compilation pipeline828/// TODO delete this, libsystem must be resolved when setting up the compilation pipeline
...@@ -825,7 +832,8 @@ pub fn resolveLibSystem(...@@ -825,7 +832,8 @@ pub fn resolveLibSystem(
825 comp: *Compilation,832 comp: *Compilation,
826 out_libs: anytype,833 out_libs: anytype,
827) !void {834) !void {
828 const diags = &self.base.comp.link_diags;835 const io = comp.io;
836 const diags = &comp.link_diags;
829837
830 var test_path = std.array_list.Managed(u8).init(arena);838 var test_path = std.array_list.Managed(u8).init(arena);
831 var checked_paths = std.array_list.Managed([]const u8).init(arena);839 var checked_paths = std.array_list.Managed([]const u8).init(arena);
...@@ -834,16 +842,16 @@ pub fn resolveLibSystem(...@@ -834,16 +842,16 @@ pub fn resolveLibSystem(
834 if (self.sdk_layout) |sdk_layout| switch (sdk_layout) {842 if (self.sdk_layout) |sdk_layout| switch (sdk_layout) {
835 .sdk => {843 .sdk => {
836 const dir = try fs.path.join(arena, &.{ comp.sysroot.?, "usr", "lib" });844 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;
838 },846 },
839 .vendored => {847 .vendored => {
840 const dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "darwin" });848 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;
842 },850 },
843 };851 };
844852
845 for (self.lib_directories) |directory| {853 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;
847 }855 }
848856
849 diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{});857 diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{});
...@@ -861,6 +869,9 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {...@@ -861,6 +869,9 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
861 const tracy = trace(@src());869 const tracy = trace(@src());
862 defer tracy.end();870 defer tracy.end();
863871
872 const comp = self.base.comp;
873 const io = comp.io;
874
864 const path, const file = input.pathAndFile().?;875 const path, const file = input.pathAndFile().?;
865 // TODO don't classify now, it's too late. The input file has already been classified876 // TODO don't classify now, it's too late. The input file has already been classified
866 log.debug("classifying input file {f}", .{path});877 log.debug("classifying input file {f}", .{path});
...@@ -871,7 +882,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {...@@ -871,7 +882,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
871 const fat_arch: ?fat.Arch = try self.parseFatFile(file, path);882 const fat_arch: ?fat.Arch = try self.parseFatFile(file, path);
872 const offset = if (fat_arch) |fa| fa.offset else 0;883 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: {
875 if (h.magic != macho.MH_MAGIC_64) break :blk;886 if (h.magic != macho.MH_MAGIC_64) break :blk;
876 switch (h.filetype) {887 switch (h.filetype) {
877 macho.MH_OBJECT => try self.addObject(path, fh, offset),888 macho.MH_OBJECT => try self.addObject(path, fh, offset),
...@@ -880,7 +891,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {...@@ -880,7 +891,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
880 }891 }
881 return;892 return;
882 }893 }
883 if (readArMagic(file, offset, &buffer) catch null) |ar_magic| blk: {894 if (readArMagic(io, file, offset, &buffer) catch null) |ar_magic| blk: {
884 if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk;895 if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk;
885 try self.addArchive(input.archive, fh, fat_arch);896 try self.addArchive(input.archive, fh, fat_arch);
886 return;897 return;
...@@ -888,12 +899,14 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {...@@ -888,12 +899,14 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
888 _ = try self.addTbd(.fromLinkInput(input), true, fh);899 _ = try self.addTbd(.fromLinkInput(input), true, fh);
889}900}
890901
891fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch {902fn parseFatFile(self: *MachO, file: Io.File, path: Path) !?fat.Arch {
892 const diags = &self.base.comp.link_diags;903 const comp = self.base.comp;
893 const fat_h = fat.readFatHeader(file) catch return null;904 const io = comp.io;
905 const diags = &comp.link_diags;
906 const fat_h = fat.readFatHeader(io, file) catch return null;
894 if (fat_h.magic != macho.FAT_MAGIC and fat_h.magic != macho.FAT_MAGIC_64) return null;907 if (fat_h.magic != macho.FAT_MAGIC and fat_h.magic != macho.FAT_MAGIC_64) return null;
895 var fat_archs_buffer: [2]fat.Arch = undefined;908 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);
897 const cpu_arch = self.getTarget().cpu.arch;910 const cpu_arch = self.getTarget().cpu.arch;
898 for (fat_archs) |arch| {911 for (fat_archs) |arch| {
899 if (arch.tag == cpu_arch) return arch;912 if (arch.tag == cpu_arch) return arch;
...@@ -901,16 +914,16 @@ fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch {...@@ -901,16 +914,16 @@ fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch {
901 return diags.failParse(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)});914 return diags.failParse(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)});
902}915}
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 {
905 var buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;918 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);
907 if (nread != buffer.len) return error.InputOutput;920 if (nread != buffer.len) return error.InputOutput;
908 const hdr = @as(*align(1) const macho.mach_header_64, @ptrCast(&buffer)).*;921 const hdr = @as(*align(1) const macho.mach_header_64, @ptrCast(&buffer)).*;
909 return hdr;922 return hdr;
910}923}
911924
912pub fn readArMagic(file: std.fs.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 {925pub fn readArMagic(io: Io, file: Io.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 {
913 const nread = try file.preadAll(buffer, offset);926 const nread = try file.readPositionalAll(io, buffer, offset);
914 if (nread != buffer.len) return error.InputOutput;927 if (nread != buffer.len) return error.InputOutput;
915 return buffer[0..Archive.SARMAG];928 return buffer[0..Archive.SARMAG];
916}929}
...@@ -921,6 +934,7 @@ fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u...@@ -921,6 +934,7 @@ fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u
921934
922 const comp = self.base.comp;935 const comp = self.base.comp;
923 const gpa = comp.gpa;936 const gpa = comp.gpa;
937 const io = comp.io;
924938
925 const abs_path = try std.fs.path.resolvePosix(gpa, &.{939 const abs_path = try std.fs.path.resolvePosix(gpa, &.{
926 comp.dirs.cwd,940 comp.dirs.cwd,
...@@ -930,7 +944,7 @@ fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u...@@ -930,7 +944,7 @@ fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u
930 errdefer gpa.free(abs_path);944 errdefer gpa.free(abs_path);
931945
932 const file = self.getFileHandle(handle_index);946 const file = self.getFileHandle(handle_index);
933 const stat = try file.stat();947 const stat = try file.stat(io);
934 const mtime = stat.mtime.toSeconds();948 const mtime = stat.mtime.toSeconds();
935 const index: File.Index = @intCast(try self.files.addOne(gpa));949 const index: File.Index = @intCast(try self.files.addOne(gpa));
936 self.files.set(index, .{ .object = .{950 self.files.set(index, .{ .object = .{
...@@ -1069,6 +1083,7 @@ fn isHoisted(self: *MachO, install_name: []const u8) bool {...@@ -1069,6 +1083,7 @@ fn isHoisted(self: *MachO, install_name: []const u8) bool {
1069/// TODO delete this, libraries must be instead resolved when instantiating the compilation pipeline1083/// TODO delete this, libraries must be instead resolved when instantiating the compilation pipeline
1070fn accessLibPath(1084fn accessLibPath(
1071 arena: Allocator,1085 arena: Allocator,
1086 io: Io,
1072 test_path: *std.array_list.Managed(u8),1087 test_path: *std.array_list.Managed(u8),
1073 checked_paths: *std.array_list.Managed([]const u8),1088 checked_paths: *std.array_list.Managed([]const u8),
1074 search_dir: []const u8,1089 search_dir: []const u8,
...@@ -1080,7 +1095,7 @@ fn accessLibPath(...@@ -1080,7 +1095,7 @@ fn accessLibPath(
1080 test_path.clearRetainingCapacity();1095 test_path.clearRetainingCapacity();
1081 try test_path.print("{s}" ++ sep ++ "lib{s}{s}", .{ search_dir, name, ext });1096 try test_path.print("{s}" ++ sep ++ "lib{s}{s}", .{ search_dir, name, ext });
1082 try checked_paths.append(try arena.dupe(u8, test_path.items));1097 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) {
1084 error.FileNotFound => continue,1099 error.FileNotFound => continue,
1085 else => |e| return e,1100 else => |e| return e,
1086 };1101 };
...@@ -1092,6 +1107,7 @@ fn accessLibPath(...@@ -1092,6 +1107,7 @@ fn accessLibPath(
10921107
1093fn accessFrameworkPath(1108fn accessFrameworkPath(
1094 arena: Allocator,1109 arena: Allocator,
1110 io: Io,
1095 test_path: *std.array_list.Managed(u8),1111 test_path: *std.array_list.Managed(u8),
1096 checked_paths: *std.array_list.Managed([]const u8),1112 checked_paths: *std.array_list.Managed([]const u8),
1097 search_dir: []const u8,1113 search_dir: []const u8,
...@@ -1108,7 +1124,7 @@ fn accessFrameworkPath(...@@ -1108,7 +1124,7 @@ fn accessFrameworkPath(
1108 ext,1124 ext,
1109 });1125 });
1110 try checked_paths.append(try arena.dupe(u8, test_path.items));1126 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) {
1112 error.FileNotFound => continue,1128 error.FileNotFound => continue,
1113 else => |e| return e,1129 else => |e| return e,
1114 };1130 };
...@@ -1124,7 +1140,9 @@ fn parseDependentDylibs(self: *MachO) !void {...@@ -1124,7 +1140,9 @@ fn parseDependentDylibs(self: *MachO) !void {
11241140
1125 if (self.dylibs.items.len == 0) return;1141 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;
1128 const framework_dirs = self.framework_dirs;1146 const framework_dirs = self.framework_dirs;
11291147
1130 // TODO delete this, directories must instead be resolved by the frontend1148 // TODO delete this, directories must instead be resolved by the frontend
...@@ -1165,14 +1183,14 @@ fn parseDependentDylibs(self: *MachO) !void {...@@ -1165,14 +1183,14 @@ fn parseDependentDylibs(self: *MachO) !void {
1165 // Framework1183 // Framework
1166 for (framework_dirs) |dir| {1184 for (framework_dirs) |dir| {
1167 test_path.clearRetainingCapacity();1185 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;
1169 }1187 }
11701188
1171 // Library1189 // Library
1172 const lib_name = eatPrefix(stem, "lib") orelse stem;1190 const lib_name = eatPrefix(stem, "lib") orelse stem;
1173 for (lib_directories) |lib_directory| {1191 for (lib_directories) |lib_directory| {
1174 test_path.clearRetainingCapacity();1192 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;
1176 }1194 }
1177 }1195 }
11781196
...@@ -1181,13 +1199,13 @@ fn parseDependentDylibs(self: *MachO) !void {...@@ -1181,13 +1199,13 @@ fn parseDependentDylibs(self: *MachO) !void {
1181 const path = if (existing_ext.len > 0) id.name[0 .. id.name.len - existing_ext.len] else id.name;1199 const path = if (existing_ext.len > 0) id.name[0 .. id.name.len - existing_ext.len] else id.name;
1182 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {1200 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
1183 test_path.clearRetainingCapacity();1201 test_path.clearRetainingCapacity();
1184 if (self.base.comp.sysroot) |root| {1202 if (comp.sysroot) |root| {
1185 try test_path.print("{s}" ++ fs.path.sep_str ++ "{s}{s}", .{ root, path, ext });1203 try test_path.print("{s}" ++ fs.path.sep_str ++ "{s}{s}", .{ root, path, ext });
1186 } else {1204 } else {
1187 try test_path.print("{s}{s}", .{ path, ext });1205 try test_path.print("{s}{s}", .{ path, ext });
1188 }1206 }
1189 try checked_paths.append(try arena.dupe(u8, test_path.items));1207 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) {
1191 error.FileNotFound => continue,1209 error.FileNotFound => continue,
1192 else => |e| return e,1210 else => |e| return e,
1193 };1211 };
...@@ -1202,7 +1220,8 @@ fn parseDependentDylibs(self: *MachO) !void {...@@ -1202,7 +1220,8 @@ fn parseDependentDylibs(self: *MachO) !void {
1202 const rel_path = try fs.path.join(arena, &.{ prefix, path });1220 const rel_path = try fs.path.join(arena, &.{ prefix, path });
1203 try checked_paths.append(rel_path);1221 try checked_paths.append(rel_path);
1204 var buffer: [fs.max_path_bytes]u8 = undefined;1222 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];
1206 break :full_path try arena.dupe(u8, full_path);1225 break :full_path try arena.dupe(u8, full_path);
1207 }1226 }
1208 } else if (eatPrefix(id.name, "@loader_path/")) |_| {1227 } else if (eatPrefix(id.name, "@loader_path/")) |_| {
...@@ -1215,8 +1234,9 @@ fn parseDependentDylibs(self: *MachO) !void {...@@ -1215,8 +1234,9 @@ fn parseDependentDylibs(self: *MachO) !void {
12151234
1216 try checked_paths.append(try arena.dupe(u8, id.name));1235 try checked_paths.append(try arena.dupe(u8, id.name));
1217 var buffer: [fs.max_path_bytes]u8 = undefined;1236 var buffer: [fs.max_path_bytes]u8 = undefined;
1218 if (fs.realpath(id.name, &buffer)) |full_path| {1237 // TODO don't use realpath
1219 break :full_path try arena.dupe(u8, full_path);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]);
1220 } else |_| {1240 } else |_| {
1221 try self.reportMissingDependencyError(1241 try self.reportMissingDependencyError(
1222 self.getFile(dylib_index).?.dylib.getUmbrella(self).index,1242 self.getFile(dylib_index).?.dylib.getUmbrella(self).index,
...@@ -1233,12 +1253,12 @@ fn parseDependentDylibs(self: *MachO) !void {...@@ -1233,12 +1253,12 @@ fn parseDependentDylibs(self: *MachO) !void {
1233 .path = Path.initCwd(full_path),1253 .path = Path.initCwd(full_path),
1234 .weak = is_weak,1254 .weak = is_weak,
1235 };1255 };
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, .{});
1237 const fh = try self.addFileHandle(file);1257 const fh = try self.addFileHandle(file);
1238 const fat_arch = try self.parseFatFile(file, lib.path);1258 const fat_arch = try self.parseFatFile(file, lib.path);
1239 const offset = if (fat_arch) |fa| fa.offset else 0;1259 const offset = if (fat_arch) |fa| fa.offset else 0;
1240 const file_index = file_index: {1260 const file_index = file_index: {
1241 if (readMachHeader(file, offset) catch null) |h| blk: {1261 if (readMachHeader(io, file, offset) catch null) |h| blk: {
1242 if (h.magic != macho.MH_MAGIC_64) break :blk;1262 if (h.magic != macho.MH_MAGIC_64) break :blk;
1243 switch (h.filetype) {1263 switch (h.filetype) {
1244 macho.MH_DYLIB => break :file_index try self.addDylib(lib, false, fh, offset),1264 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 {...@@ -3147,7 +3167,9 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) !?u64 {
3147 }3167 }
3148 }3168 }
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);
3151 return null;3173 return null;
3152}3174}
31533175
...@@ -3232,21 +3254,36 @@ pub fn findFreeSpaceVirtual(self: *MachO, object_size: u64, min_alignment: u32)...@@ -3232,21 +3254,36 @@ pub fn findFreeSpaceVirtual(self: *MachO, object_size: u64, min_alignment: u32)
3232}3254}
32333255
3234pub fn copyRangeAll(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {3256pub fn copyRangeAll(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {
3235 const file = self.base.file.?;3257 return self.base.copyRangeAll(old_offset, new_offset, size);
3236 const amt = try file.copyRangeAll(old_offset, file, new_offset, size);
3237 if (amt != size) return error.InputOutput;
3238}3258}
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.
3241/// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader.3261/// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader.
3242fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {3262fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {
3243 const gpa = self.base.comp.gpa;3263 const comp = self.base.comp;
3244 try self.copyRangeAll(old_offset, new_offset, size);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;
3245 const size_u = math.cast(usize, size) orelse return error.Overflow;3271 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.3272 const n = file_writer.interface.sendFileAll(&file_reader, .limited(size_u)) catch |err| switch (err) {
3247 defer gpa.free(zeroes);3273 error.ReadFailed => return file_reader.err.?,
3248 @memset(zeroes, 0);3274 error.WriteFailed => return file_writer.err.?,
3249 try self.base.file.?.pwriteAll(zeroes, old_offset);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 };
3250}3287}
32513288
3252const InitMetadataOptions = struct {3289const InitMetadataOptions = struct {
...@@ -3257,8 +3294,10 @@ const InitMetadataOptions = struct {...@@ -3257,8 +3294,10 @@ const InitMetadataOptions = struct {
3257};3294};
32583295
3259pub fn closeDebugInfo(self: *MachO) bool {3296pub fn closeDebugInfo(self: *MachO) bool {
3297 const comp = self.base.comp;
3298 const io = comp.io;
3260 const d_sym = &(self.d_sym orelse return false);3299 const d_sym = &(self.d_sym orelse return false);
3261 d_sym.file.?.close();3300 d_sym.file.?.close(io);
3262 d_sym.file = null;3301 d_sym.file = null;
3263 return true;3302 return true;
3264}3303}
...@@ -3269,7 +3308,9 @@ pub fn reopenDebugInfo(self: *MachO) !void {...@@ -3269,7 +3308,9 @@ pub fn reopenDebugInfo(self: *MachO) !void {
3269 assert(!self.base.comp.config.use_llvm);3308 assert(!self.base.comp.config.use_llvm);
3270 assert(self.base.comp.config.debug_format == .dwarf);3309 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;
3273 const sep = fs.path.sep_str;3314 const sep = fs.path.sep_str;
3274 const d_sym_path = try std.fmt.allocPrint(3315 const d_sym_path = try std.fmt.allocPrint(
3275 gpa,3316 gpa,
...@@ -3278,10 +3319,10 @@ pub fn reopenDebugInfo(self: *MachO) !void {...@@ -3278,10 +3319,10 @@ pub fn reopenDebugInfo(self: *MachO) !void {
3278 );3319 );
3279 defer gpa.free(d_sym_path);3320 defer gpa.free(d_sym_path);
32803321
3281 var d_sym_bundle = try self.base.emit.root_dir.handle.makeOpenPath(d_sym_path, .{});3322 var d_sym_bundle = try self.base.emit.root_dir.handle.createDirPathOpen(io, d_sym_path, .{});
3282 defer d_sym_bundle.close();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), .{
3285 .truncate = false,3326 .truncate = false,
3286 .read = true,3327 .read = true,
3287 });3328 });
...@@ -3289,6 +3330,10 @@ pub fn reopenDebugInfo(self: *MachO) !void {...@@ -3289,6 +3330,10 @@ pub fn reopenDebugInfo(self: *MachO) !void {
32893330
3290// TODO: move to ZigObject3331// TODO: move to ZigObject
3291fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {3332fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
3333 const comp = self.base.comp;
3334 const gpa = comp.gpa;
3335 const io = comp.io;
3336
3292 if (!self.base.isRelocatable()) {3337 if (!self.base.isRelocatable()) {
3293 const base_vmaddr = blk: {3338 const base_vmaddr = blk: {
3294 const pagezero_size = self.pagezero_size orelse default_pagezero_size;3339 const pagezero_size = self.pagezero_size orelse default_pagezero_size;
...@@ -3343,7 +3388,11 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {...@@ -3343,7 +3388,11 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
3343 if (options.zo.dwarf) |*dwarf| {3388 if (options.zo.dwarf) |*dwarf| {
3344 // Create dSYM bundle.3389 // Create dSYM bundle.
3345 log.debug("creating {s}.dSYM bundle", .{options.emit.sub_path});3390 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 };
3347 try self.reopenDebugInfo();3396 try self.reopenDebugInfo();
3348 try self.d_sym.?.initMetadata(self);3397 try self.d_sym.?.initMetadata(self);
3349 try dwarf.initMetadata();3398 try dwarf.initMetadata();
...@@ -3463,6 +3512,9 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo...@@ -3463,6 +3512,9 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo
3463 const seg_id = self.sections.items(.segment_id)[sect_index];3512 const seg_id = self.sections.items(.segment_id)[sect_index];
3464 const seg = &self.segments.items[seg_id];3513 const seg = &self.segments.items[seg_id];
34653514
3515 const comp = self.base.comp;
3516 const io = comp.io;
3517
3466 if (!sect.isZerofill()) {3518 if (!sect.isZerofill()) {
3467 const allocated_size = self.allocatedSize(sect.offset);3519 const allocated_size = self.allocatedSize(sect.offset);
3468 if (needed_size > allocated_size) {3520 if (needed_size > allocated_size) {
...@@ -3484,7 +3536,7 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo...@@ -3484,7 +3536,7 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo
34843536
3485 sect.offset = @intCast(new_offset);3537 sect.offset = @intCast(new_offset);
3486 } else if (sect.offset + allocated_size == std.math.maxInt(u64)) {3538 } 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);
3488 }3540 }
3489 seg.filesize = needed_size;3541 seg.filesize = needed_size;
3490 }3542 }
...@@ -3506,6 +3558,8 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo...@@ -3506,6 +3558,8 @@ fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !vo
3506}3558}
35073559
3508fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void {3560fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void {
3561 const comp = self.base.comp;
3562 const io = comp.io;
3509 const sect = &self.sections.items(.header)[sect_index];3563 const sect = &self.sections.items(.header)[sect_index];
35103564
3511 if (!sect.isZerofill()) {3565 if (!sect.isZerofill()) {
...@@ -3533,7 +3587,7 @@ fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void...@@ -3533,7 +3587,7 @@ fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void
3533 sect.offset = @intCast(new_offset);3587 sect.offset = @intCast(new_offset);
3534 sect.addr = new_addr;3588 sect.addr = new_addr;
3535 } else if (sect.offset + allocated_size == std.math.maxInt(u64)) {3589 } 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);
3537 }3591 }
3538 }3592 }
3539 sect.size = needed_size;3593 sect.size = needed_size;
...@@ -3567,11 +3621,11 @@ pub fn getTarget(self: *const MachO) *const std.Target {...@@ -3567,11 +3621,11 @@ pub fn getTarget(self: *const MachO) *const std.Target {
3567/// into a new inode, remove the original file, and rename the copy to match3621/// into a new inode, remove the original file, and rename the copy to match
3568/// the original file. This is super messy, but there doesn't seem any other3622/// the original file. This is super messy, but there doesn't seem any other
3569/// way to please the XNU.3623/// 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 {
3571 const tracy = trace(@src());3625 const tracy = trace(@src());
3572 defer tracy.end();3626 defer tracy.end();
3573 if (builtin.target.os.tag.isDarwin() and builtin.target.cpu.arch == .aarch64) {3627 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, .{});
3575 }3629 }
3576}3630}
35773631
...@@ -3762,7 +3816,7 @@ pub fn getInternalObject(self: *MachO) ?*InternalObject {...@@ -3762,7 +3816,7 @@ pub fn getInternalObject(self: *MachO) ?*InternalObject {
3762 return self.getFile(index).?.internal;3816 return self.getFile(index).?.internal;
3763}3817}
37643818
3765pub fn addFileHandle(self: *MachO, file: fs.File) !File.HandleIndex {3819pub fn addFileHandle(self: *MachO, file: Io.File) !File.HandleIndex {
3766 const gpa = self.base.comp.gpa;3820 const gpa = self.base.comp.gpa;
3767 const index: File.HandleIndex = @intCast(self.file_handles.items.len);3821 const index: File.HandleIndex = @intCast(self.file_handles.items.len);
3768 const fh = try self.file_handles.addOne(gpa);3822 const fh = try self.file_handles.addOne(gpa);
...@@ -4333,11 +4387,13 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi...@@ -4333,11 +4387,13 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi
4333 defer arena_allocator.deinit();4387 defer arena_allocator.deinit();
4334 const arena = arena_allocator.allocator();4388 const arena = arena_allocator.allocator();
43354389
4390 const io = comp.io;
4391
4336 const sdk_dir = switch (sdk_layout) {4392 const sdk_dir = switch (sdk_layout) {
4337 .sdk => comp.sysroot.?,4393 .sdk => comp.sysroot.?,
4338 .vendored => fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "libc", "darwin" }) catch return null,4394 .vendored => fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "libc", "darwin" }) catch return null,
4339 };4395 };
4340 if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| {4396 if (readSdkVersionFromSettings(arena, io, sdk_dir)) |ver| {
4341 return parseSdkVersion(ver);4397 return parseSdkVersion(ver);
4342 } else |_| {4398 } else |_| {
4343 // Read from settings should always succeed when vendored.4399 // Read from settings should always succeed when vendored.
...@@ -4360,9 +4416,9 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi...@@ -4360,9 +4416,9 @@ fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersi
4360// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.4416// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
4361// Use property `MinimalDisplayName` to determine version.4417// Use property `MinimalDisplayName` to determine version.
4362// The file/property is also available with vendored libc.4418// 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 {
4364 const sdk_path = try fs.path.join(arena, &.{ dir, "SDKSettings.json" });4420 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)));
4366 const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});4422 const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
4367 if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;4423 if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
4368 return error.SdkVersionFailure;4424 return error.SdkVersionFailure;
...@@ -5324,18 +5380,18 @@ fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool {...@@ -5324,18 +5380,18 @@ fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool {
53245380
5325pub fn pwriteAll(macho_file: *MachO, bytes: []const u8, offset: u64) error{LinkFailure}!void {5381pub fn pwriteAll(macho_file: *MachO, bytes: []const u8, offset: u64) error{LinkFailure}!void {
5326 const comp = macho_file.base.comp;5382 const comp = macho_file.base.comp;
5383 const io = comp.io;
5327 const diags = &comp.link_diags;5384 const diags = &comp.link_diags;
5328 macho_file.base.file.?.pwriteAll(bytes, offset) catch |err| {5385 macho_file.base.file.?.writePositionalAll(io, bytes, offset) catch |err|
5329 return diags.fail("failed to write: {s}", .{@errorName(err)});5386 return diags.fail("failed to write: {t}", .{err});
5330 };
5331}5387}
53325388
5333pub fn setEndPos(macho_file: *MachO, length: u64) error{LinkFailure}!void {5389pub fn setLength(macho_file: *MachO, length: u64) error{LinkFailure}!void {
5334 const comp = macho_file.base.comp;5390 const comp = macho_file.base.comp;
5391 const io = comp.io;
5335 const diags = &comp.link_diags;5392 const diags = &comp.link_diags;
5336 macho_file.base.file.?.setEndPos(length) catch |err| {5393 macho_file.base.file.?.setLength(io, length) catch |err|
5337 return diags.fail("failed to set file end pos: {s}", .{@errorName(err)});5394 return diags.fail("failed to set file end pos: {t}", .{err});
5338 };
5339}5395}
53405396
5341pub fn cast(macho_file: *MachO, comptime T: type, x: anytype) error{LinkFailure}!T {5397pub fn cast(macho_file: *MachO, comptime T: type, x: anytype) error{LinkFailure}!T {
...@@ -5367,10 +5423,11 @@ const max_distance = (1 << (jump_bits - 1));...@@ -5367,10 +5423,11 @@ const max_distance = (1 << (jump_bits - 1));
5367const max_allowed_distance = max_distance - 0x500_000;5423const max_allowed_distance = max_distance - 0x500_000;
53685424
5369const MachO = @This();5425const MachO = @This();
5370
5371const std = @import("std");
5372const build_options = @import("build_options");5426const build_options = @import("build_options");
5373const builtin = @import("builtin");5427const builtin = @import("builtin");
5428
5429const std = @import("std");
5430const Io = std.Io;
5374const assert = std.debug.assert;5431const assert = std.debug.assert;
5375const fs = std.fs;5432const fs = std.fs;
5376const log = std.log.scoped(.link);5433const log = std.log.scoped(.link);
...@@ -5380,6 +5437,11 @@ const math = std.math;...@@ -5380,6 +5437,11 @@ const math = std.math;
5380const mem = std.mem;5437const mem = std.mem;
5381const meta = std.meta;5438const meta = std.meta;
5382const Writer = std.Io.Writer;5439const 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
5384const aarch64 = codegen.aarch64.encoding;5446const aarch64 = codegen.aarch64.encoding;
5385const bind = @import("MachO/dyld_info/bind.zig");5447const bind = @import("MachO/dyld_info/bind.zig");
...@@ -5397,11 +5459,8 @@ const trace = @import("../tracy.zig").trace;...@@ -5397,11 +5459,8 @@ const trace = @import("../tracy.zig").trace;
5397const synthetic = @import("MachO/synthetic.zig");5459const synthetic = @import("MachO/synthetic.zig");
53985460
5399const Alignment = Atom.Alignment;5461const Alignment = Atom.Alignment;
5400const Allocator = mem.Allocator;
5401const Archive = @import("MachO/Archive.zig");5462const Archive = @import("MachO/Archive.zig");
5402const AtomicBool = std.atomic.Value(bool);
5403const Bind = bind.Bind;5463const Bind = bind.Bind;
5404const Cache = std.Build.Cache;
5405const CodeSignature = @import("MachO/CodeSignature.zig");5464const CodeSignature = @import("MachO/CodeSignature.zig");
5406const Compilation = @import("../Compilation.zig");5465const Compilation = @import("../Compilation.zig");
5407const DataInCode = synthetic.DataInCode;5466const DataInCode = synthetic.DataInCode;
...@@ -5411,14 +5470,12 @@ const ExportTrie = @import("MachO/dyld_info/Trie.zig");...@@ -5411,14 +5470,12 @@ const ExportTrie = @import("MachO/dyld_info/Trie.zig");
5411const Path = Cache.Path;5470const Path = Cache.Path;
5412const File = @import("MachO/file.zig").File;5471const File = @import("MachO/file.zig").File;
5413const GotSection = synthetic.GotSection;5472const GotSection = synthetic.GotSection;
5414const Hash = std.hash.Wyhash;
5415const Indsymtab = synthetic.Indsymtab;5473const Indsymtab = synthetic.Indsymtab;
5416const InternalObject = @import("MachO/InternalObject.zig");5474const InternalObject = @import("MachO/InternalObject.zig");
5417const ObjcStubsSection = synthetic.ObjcStubsSection;5475const ObjcStubsSection = synthetic.ObjcStubsSection;
5418const Object = @import("MachO/Object.zig");5476const Object = @import("MachO/Object.zig");
5419const LazyBind = bind.LazyBind;5477const LazyBind = bind.LazyBind;
5420const LaSymbolPtrSection = synthetic.LaSymbolPtrSection;5478const LaSymbolPtrSection = synthetic.LaSymbolPtrSection;
5421const Md5 = std.crypto.hash.Md5;
5422const Zcu = @import("../Zcu.zig");5479const Zcu = @import("../Zcu.zig");
5423const InternPool = @import("../InternPool.zig");5480const InternPool = @import("../InternPool.zig");
5424const Rebase = @import("MachO/dyld_info/Rebase.zig");5481const 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 {...@@ -6,6 +6,7 @@ pub fn deinit(self: *Archive, allocator: Allocator) void {
66
7pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void {7pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void {
8 const comp = macho_file.base.comp;8 const comp = macho_file.base.comp;
9 const io = comp.io;
9 const gpa = comp.gpa;10 const gpa = comp.gpa;
10 const diags = &comp.link_diags;11 const diags = &comp.link_diags;
1112
...@@ -14,7 +15,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File...@@ -14,7 +15,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File
1415
15 const handle = macho_file.getFileHandle(handle_index);16 const handle = macho_file.getFileHandle(handle_index);
16 const offset = if (fat_arch) |ar| ar.offset else 0;17 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
19 var pos: usize = offset + SARMAG;20 var pos: usize = offset + SARMAG;
20 while (true) {21 while (true) {
...@@ -23,7 +24,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File...@@ -23,7 +24,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File
2324
24 var hdr_buffer: [@sizeOf(ar_hdr)]u8 = undefined;25 var hdr_buffer: [@sizeOf(ar_hdr)]u8 = undefined;
25 {26 {
26 const amt = try handle.preadAll(&hdr_buffer, pos);27 const amt = try handle.readPositionalAll(io, &hdr_buffer, pos);
27 if (amt != @sizeOf(ar_hdr)) return error.InputOutput;28 if (amt != @sizeOf(ar_hdr)) return error.InputOutput;
28 }29 }
29 const hdr = @as(*align(1) const ar_hdr, @ptrCast(&hdr_buffer)).*;30 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...@@ -41,7 +42,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File
41 if (try hdr.nameLength()) |len| {42 if (try hdr.nameLength()) |len| {
42 hdr_size -= len;43 hdr_size -= len;
43 const buf = try arena.allocator().alloc(u8, len);44 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);
45 if (amt != len) return error.InputOutput;46 if (amt != len) return error.InputOutput;
46 pos += len;47 pos += len;
47 const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len;48 const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len;
src/link/MachO/CodeSignature.zig+22-19
...@@ -1,20 +1,28 @@...@@ -1,20 +1,28 @@
1const CodeSignature = @This();1const CodeSignature = @This();
22
3const std = @import("std");3const std = @import("std");
4const Io = std.Io;
4const assert = std.debug.assert;5const assert = std.debug.assert;
5const fs = std.fs;6const fs = std.fs;
6const log = std.log.scoped(.link);7const log = std.log.scoped(.link);
7const macho = std.macho;8const macho = std.macho;
8const mem = std.mem;9const mem = std.mem;
9const testing = std.testing;10const testing = std.testing;
11const Sha256 = std.crypto.hash.sha2.Sha256;
12const Allocator = std.mem.Allocator;
13
10const trace = @import("../../tracy.zig").trace;14const trace = @import("../../tracy.zig").trace;
11const Allocator = mem.Allocator;15const ParallelHasher = @import("hasher.zig").ParallelHasher;
12const Hasher = @import("hasher.zig").ParallelHasher;
13const MachO = @import("../MachO.zig");16const MachO = @import("../MachO.zig");
14const Sha256 = std.crypto.hash.sha2.Sha256;
1517
16const hash_size = Sha256.digest_length;18const hash_size = Sha256.digest_length;
1719
20page_size: u16,
21code_directory: CodeDirectory,
22requirements: ?Requirements = null,
23entitlements: ?Entitlements = null,
24signature: ?Signature = null,
25
18const Blob = union(enum) {26const Blob = union(enum) {
19 code_directory: *CodeDirectory,27 code_directory: *CodeDirectory,
20 requirements: *Requirements,28 requirements: *Requirements,
...@@ -218,12 +226,6 @@ const Signature = struct {...@@ -218,12 +226,6 @@ const Signature = struct {
218 }226 }
219};227};
220228
221page_size: u16,
222code_directory: CodeDirectory,
223requirements: ?Requirements = null,
224entitlements: ?Entitlements = null,
225signature: ?Signature = null,
226
227pub fn init(page_size: u16) CodeSignature {229pub fn init(page_size: u16) CodeSignature {
228 return .{230 return .{
229 .page_size = page_size,231 .page_size = page_size,
...@@ -244,13 +246,13 @@ pub fn deinit(self: *CodeSignature, allocator: Allocator) void {...@@ -244,13 +246,13 @@ pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
244 }246 }
245}247}
246248
247pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {249pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, io: Io, path: []const u8) !void {
248 const inner = try fs.cwd().readFileAlloc(path, allocator, .limited(std.math.maxInt(u32)));250 const inner = try Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(std.math.maxInt(u32)));
249 self.entitlements = .{ .inner = inner };251 self.entitlements = .{ .inner = inner };
250}252}
251253
252pub const WriteOpts = struct {254pub const WriteOpts = struct {
253 file: fs.File,255 file: Io.File,
254 exec_seg_base: u64,256 exec_seg_base: u64,
255 exec_seg_limit: u64,257 exec_seg_limit: u64,
256 file_size: u32,258 file_size: u32,
...@@ -266,7 +268,9 @@ pub fn writeAdhocSignature(...@@ -266,7 +268,9 @@ pub fn writeAdhocSignature(
266 const tracy = trace(@src());268 const tracy = trace(@src());
267 defer tracy.end();269 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
271 var header: macho.SuperBlob = .{275 var header: macho.SuperBlob = .{
272 .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,276 .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
...@@ -274,7 +278,7 @@ pub fn writeAdhocSignature(...@@ -274,7 +278,7 @@ pub fn writeAdhocSignature(
274 .count = 0,278 .count = 0,
275 };279 };
276280
277 var blobs = std.array_list.Managed(Blob).init(allocator);281 var blobs = std.array_list.Managed(Blob).init(gpa);
278 defer blobs.deinit();282 defer blobs.deinit();
279283
280 self.code_directory.inner.execSegBase = opts.exec_seg_base;284 self.code_directory.inner.execSegBase = opts.exec_seg_base;
...@@ -284,13 +288,12 @@ pub fn writeAdhocSignature(...@@ -284,13 +288,12 @@ pub fn writeAdhocSignature(
284288
285 const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));289 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);
288 self.code_directory.code_slots.items.len = total_pages;292 self.code_directory.code_slots.items.len = total_pages;
289 self.code_directory.inner.nCodeSlots = total_pages;293 self.code_directory.inner.nCodeSlots = total_pages;
290294
291 // Calculate hash for each page (in file) and write it to the buffer295 // 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 };296 try ParallelHasher(Sha256).hash(gpa, io, opts.file, self.code_directory.code_slots.items, .{
293 try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
294 .chunk_size = self.page_size,297 .chunk_size = self.page_size,
295 .max_file_size = opts.file_size,298 .max_file_size = opts.file_size,
296 });299 });
...@@ -302,7 +305,7 @@ pub fn writeAdhocSignature(...@@ -302,7 +305,7 @@ pub fn writeAdhocSignature(
302 var hash: [hash_size]u8 = undefined;305 var hash: [hash_size]u8 = undefined;
303306
304 if (self.requirements) |*req| {307 if (self.requirements) |*req| {
305 var a: std.Io.Writer.Allocating = .init(allocator);308 var a: std.Io.Writer.Allocating = .init(gpa);
306 defer a.deinit();309 defer a.deinit();
307 try req.write(&a.writer);310 try req.write(&a.writer);
308 Sha256.hash(a.written(), &hash, .{});311 Sha256.hash(a.written(), &hash, .{});
...@@ -314,7 +317,7 @@ pub fn writeAdhocSignature(...@@ -314,7 +317,7 @@ pub fn writeAdhocSignature(
314 }317 }
315318
316 if (self.entitlements) |*ents| {319 if (self.entitlements) |*ents| {
317 var a: std.Io.Writer.Allocating = .init(allocator);320 var a: std.Io.Writer.Allocating = .init(gpa);
318 defer a.deinit();321 defer a.deinit();
319 try ents.write(&a.writer);322 try ents.write(&a.writer);
320 Sha256.hash(a.written(), &hash, .{});323 Sha256.hash(a.written(), &hash, .{});
src/link/MachO/DebugSymbols.zig+45-46
...@@ -1,5 +1,28 @@...@@ -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,
1allocator: Allocator,24allocator: Allocator,
2file: ?fs.File,25file: ?Io.File,
326
4symtab_cmd: macho.symtab_command = .{},27symtab_cmd: macho.symtab_command = .{},
5uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 },28uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 },
...@@ -102,6 +125,7 @@ pub fn growSection(...@@ -102,6 +125,7 @@ pub fn growSection(
102 requires_file_copy: bool,125 requires_file_copy: bool,
103 macho_file: *MachO,126 macho_file: *MachO,
104) !void {127) !void {
128 const io = self.io;
105 const sect = self.getSectionPtr(sect_index);129 const sect = self.getSectionPtr(sect_index);
106130
107 const allocated_size = self.allocatedSize(sect.offset);131 const allocated_size = self.allocatedSize(sect.offset);
...@@ -111,25 +135,17 @@ pub fn growSection(...@@ -111,25 +135,17 @@ pub fn growSection(
111 const new_offset = try self.findFreeSpace(needed_size, 1);135 const new_offset = try self.findFreeSpace(needed_size, 1);
112136
113 log.debug("moving {s} section: {} bytes from 0x{x} to 0x{x}", .{137 log.debug("moving {s} section: {} bytes from 0x{x} to 0x{x}", .{
114 sect.sectName(),138 sect.sectName(), existing_size, sect.offset, new_offset,
115 existing_size,
116 sect.offset,
117 new_offset,
118 });139 });
119140
120 if (requires_file_copy) {141 if (requires_file_copy) {
121 const amt = try self.file.?.copyRangeAll(142 const file = self.file.?;
122 sect.offset,143 try link.File.copyRangeAll2(io, file, file, sect.offset, new_offset, existing_size);
123 self.file.?,
124 new_offset,
125 existing_size,
126 );
127 if (amt != existing_size) return error.InputOutput;
128 }144 }
129145
130 sect.offset = @intCast(new_offset);146 sect.offset = @intCast(new_offset);
131 } else if (sect.offset + allocated_size == std.math.maxInt(u64)) {147 } 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);
133 }149 }
134150
135 sect.size = needed_size;151 sect.size = needed_size;
...@@ -153,6 +169,7 @@ pub fn markDirty(self: *DebugSymbols, sect_index: u8, macho_file: *MachO) void {...@@ -153,6 +169,7 @@ pub fn markDirty(self: *DebugSymbols, sect_index: u8, macho_file: *MachO) void {
153}169}
154170
155fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 {171fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 {
172 const io = self.io;
156 var at_end = true;173 var at_end = true;
157 const end = start + padToIdeal(size);174 const end = start + padToIdeal(size);
158175
...@@ -165,7 +182,7 @@ fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 {...@@ -165,7 +182,7 @@ fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 {
165 }182 }
166 }183 }
167184
168 if (at_end) try self.file.?.setEndPos(end);185 if (at_end) try self.file.?.setLength(io, end);
169 return null;186 return null;
170}187}
171188
...@@ -179,6 +196,7 @@ fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64...@@ -179,6 +196,7 @@ fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64
179}196}
180197
181pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {198pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {
199 const io = self.io;
182 const zo = macho_file.getZigObject().?;200 const zo = macho_file.getZigObject().?;
183 for (self.relocs.items) |*reloc| {201 for (self.relocs.items) |*reloc| {
184 const sym = zo.symbols.items[reloc.target];202 const sym = zo.symbols.items[reloc.target];
...@@ -190,12 +208,9 @@ pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {...@@ -190,12 +208,9 @@ pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {
190 const sect = &self.sections.items[self.debug_info_section_index.?];208 const sect = &self.sections.items[self.debug_info_section_index.?];
191 const file_offset = sect.offset + reloc.offset;209 const file_offset = sect.offset + reloc.offset;
192 log.debug("resolving relocation: {d}@{x} ('{s}') at offset {x}", .{210 log.debug("resolving relocation: {d}@{x} ('{s}') at offset {x}", .{
193 reloc.target,211 reloc.target, addr, sym_name, file_offset,
194 addr,
195 sym_name,
196 file_offset,
197 });212 });
198 try self.file.?.pwriteAll(mem.asBytes(&addr), file_offset);213 try self.file.?.writePositionalAll(io, mem.asBytes(&addr), file_offset);
199 }214 }
200215
201 self.finalizeDwarfSegment(macho_file);216 self.finalizeDwarfSegment(macho_file);
...@@ -208,7 +223,8 @@ pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {...@@ -208,7 +223,8 @@ pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {
208223
209pub fn deinit(self: *DebugSymbols) void {224pub fn deinit(self: *DebugSymbols) void {
210 const gpa = self.allocator;225 const gpa = self.allocator;
211 if (self.file) |file| file.close();226 const io = self.io;
227 if (self.file) |file| file.close(io);
212 self.segments.deinit(gpa);228 self.segments.deinit(gpa);
213 self.sections.deinit(gpa);229 self.sections.deinit(gpa);
214 self.relocs.deinit(gpa);230 self.relocs.deinit(gpa);
...@@ -268,6 +284,7 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void {...@@ -268,6 +284,7 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void {
268}284}
269285
270fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, usize } {286fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, usize } {
287 const io = self.io;
271 const gpa = self.allocator;288 const gpa = self.allocator;
272 const needed_size = load_commands.calcLoadCommandsSizeDsym(macho_file, self);289 const needed_size = load_commands.calcLoadCommandsSizeDsym(macho_file, self);
273 const buffer = try gpa.alloc(u8, needed_size);290 const buffer = try gpa.alloc(u8, needed_size);
...@@ -319,12 +336,13 @@ fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, u...@@ -319,12 +336,13 @@ fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, u
319336
320 assert(writer.end == needed_size);337 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
324 return .{ ncmds, buffer.len };341 return .{ ncmds, buffer.len };
325}342}
326343
327fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {344fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {
345 const io = self.io;
328 var header: macho.mach_header_64 = .{};346 var header: macho.mach_header_64 = .{};
329 header.filetype = macho.MH_DSYM;347 header.filetype = macho.MH_DSYM;
330348
...@@ -345,7 +363,7 @@ fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds...@@ -345,7 +363,7 @@ fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds
345363
346 log.debug("writing Mach-O header {}", .{header});364 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);
349}367}
350368
351fn allocatedSize(self: *DebugSymbols, start: u64) u64 {369fn allocatedSize(self: *DebugSymbols, start: u64) u64 {
...@@ -380,6 +398,8 @@ fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void {...@@ -380,6 +398,8 @@ fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void {
380pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 {398pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 {
381 const tracy = trace(@src());399 const tracy = trace(@src());
382 defer tracy.end();400 defer tracy.end();
401
402 const io = self.io;
383 const gpa = self.allocator;403 const gpa = self.allocator;
384 const cmd = &self.symtab_cmd;404 const cmd = &self.symtab_cmd;
385 cmd.nsyms = macho_file.symtab_cmd.nsyms;405 cmd.nsyms = macho_file.symtab_cmd.nsyms;
...@@ -403,15 +423,16 @@ pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 {...@@ -403,15 +423,16 @@ pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 {
403 internal.writeSymtab(macho_file, self);423 internal.writeSymtab(macho_file, self);
404 }424 }
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
408 return off + cmd.nsyms * @sizeOf(macho.nlist_64);428 return off + cmd.nsyms * @sizeOf(macho.nlist_64);
409}429}
410430
411pub fn writeStrtab(self: *DebugSymbols, off: u32) !u32 {431pub fn writeStrtab(self: *DebugSymbols, off: u32) !u32 {
432 const io = self.io;
412 const cmd = &self.symtab_cmd;433 const cmd = &self.symtab_cmd;
413 cmd.stroff = off;434 cmd.stroff = off;
414 try self.file.?.pwriteAll(self.strtab.items, cmd.stroff);435 try self.file.?.writePositionalAll(io, self.strtab.items, cmd.stroff);
415 return off + cmd.strsize;436 return off + cmd.strsize;
416}437}
417438
...@@ -443,25 +464,3 @@ pub fn getSection(self: DebugSymbols, sect: u8) macho.section_64 {...@@ -443,25 +464,3 @@ pub fn getSection(self: DebugSymbols, sect: u8) macho.section_64 {
443 assert(sect < self.sections.items.len);464 assert(sect < self.sections.items.len);
444 return self.sections.items[sect];465 return self.sections.items[sect];
445}466}
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 {...@@ -57,7 +57,9 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
57 const tracy = trace(@src());57 const tracy = trace(@src());
58 defer tracy.end();58 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;
61 const file = macho_file.getFileHandle(self.file_handle);63 const file = macho_file.getFileHandle(self.file_handle);
62 const offset = self.offset;64 const offset = self.offset;
6365
...@@ -65,7 +67,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {...@@ -65,7 +67,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
6567
66 var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;68 var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
67 {69 {
68 const amt = try file.preadAll(&header_buffer, offset);70 const amt = try file.readPositionalAll(io, &header_buffer, offset);
69 if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;71 if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;
70 }72 }
71 const header = @as(*align(1) const macho.mach_header_64, @ptrCast(&header_buffer)).*;73 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 {...@@ -86,7 +88,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
86 const lc_buffer = try gpa.alloc(u8, header.sizeofcmds);88 const lc_buffer = try gpa.alloc(u8, header.sizeofcmds);
87 defer gpa.free(lc_buffer);89 defer gpa.free(lc_buffer);
88 {90 {
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));
90 if (amt != lc_buffer.len) return error.InputOutput;92 if (amt != lc_buffer.len) return error.InputOutput;
91 }93 }
9294
...@@ -103,7 +105,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {...@@ -103,7 +105,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
103 const dyld_cmd = cmd.cast(macho.dyld_info_command).?;105 const dyld_cmd = cmd.cast(macho.dyld_info_command).?;
104 const data = try gpa.alloc(u8, dyld_cmd.export_size);106 const data = try gpa.alloc(u8, dyld_cmd.export_size);
105 defer gpa.free(data);107 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);
107 if (amt != data.len) return error.InputOutput;109 if (amt != data.len) return error.InputOutput;
108 try self.parseTrie(data, macho_file);110 try self.parseTrie(data, macho_file);
109 },111 },
...@@ -111,7 +113,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {...@@ -111,7 +113,7 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
111 const ld_cmd = cmd.cast(macho.linkedit_data_command).?;113 const ld_cmd = cmd.cast(macho.linkedit_data_command).?;
112 const data = try gpa.alloc(u8, ld_cmd.datasize);114 const data = try gpa.alloc(u8, ld_cmd.datasize);
113 defer gpa.free(data);115 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);
115 if (amt != data.len) return error.InputOutput;117 if (amt != data.len) return error.InputOutput;
116 try self.parseTrie(data, macho_file);118 try self.parseTrie(data, macho_file);
117 },119 },
...@@ -238,13 +240,15 @@ fn parseTbd(self: *Dylib, macho_file: *MachO) !void {...@@ -238,13 +240,15 @@ fn parseTbd(self: *Dylib, macho_file: *MachO) !void {
238 const tracy = trace(@src());240 const tracy = trace(@src());
239 defer tracy.end();241 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
243 log.debug("parsing dylib from stub: {f}", .{self.path});247 log.debug("parsing dylib from stub: {f}", .{self.path});
244248
245 const file = macho_file.getFileHandle(self.file_handle);249 const file = macho_file.getFileHandle(self.file_handle);
246 var lib_stub = LibStub.loadFromFile(gpa, file) catch |err| {250 var lib_stub = LibStub.loadFromFile(gpa, io, file) catch |err| {
247 try macho_file.reportParseError2(self.index, "failed to parse TBD file: {s}", .{@errorName(err)});251 try macho_file.reportParseError2(self.index, "failed to parse TBD file: {t}", .{err});
248 return error.MalformedTbd;252 return error.MalformedTbd;
249 };253 };
250 defer lib_stub.deinit();254 defer lib_stub.deinit();
src/link/MachO/Object.zig+91-62
...@@ -1,3 +1,30 @@...@@ -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
1/// Non-zero for fat object files or archives28/// Non-zero for fat object files or archives
2offset: u64,29offset: u64,
3/// If `in_archive` is not `null`, this is the basename of the object in the archive. Otherwise,30/// 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 {...@@ -75,7 +102,9 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
75102
76 log.debug("parsing {f}", .{self.fmtPath()});103 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;
79 const handle = macho_file.getFileHandle(self.file_handle);108 const handle = macho_file.getFileHandle(self.file_handle);
80 const cpu_arch = macho_file.getTarget().cpu.arch;109 const cpu_arch = macho_file.getTarget().cpu.arch;
81110
...@@ -84,7 +113,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {...@@ -84,7 +113,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
84113
85 var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;114 var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
86 {115 {
87 const amt = try handle.preadAll(&header_buffer, self.offset);116 const amt = try handle.readPositionalAll(io, &header_buffer, self.offset);
88 if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;117 if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;
89 }118 }
90 self.header = @as(*align(1) const macho.mach_header_64, @ptrCast(&header_buffer)).*;119 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 {...@@ -105,7 +134,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
105 const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds);134 const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds);
106 defer gpa.free(lc_buffer);135 defer gpa.free(lc_buffer);
107 {136 {
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));
109 if (amt != self.header.?.sizeofcmds) return error.InputOutput;138 if (amt != self.header.?.sizeofcmds) return error.InputOutput;
110 }139 }
111140
...@@ -129,14 +158,14 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {...@@ -129,14 +158,14 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
129 const cmd = lc.cast(macho.symtab_command).?;158 const cmd = lc.cast(macho.symtab_command).?;
130 try self.strtab.resize(gpa, cmd.strsize);159 try self.strtab.resize(gpa, cmd.strsize);
131 {160 {
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);
133 if (amt != self.strtab.items.len) return error.InputOutput;162 if (amt != self.strtab.items.len) return error.InputOutput;
134 }163 }
135164
136 const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64));165 const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64));
137 defer gpa.free(symtab_buffer);166 defer gpa.free(symtab_buffer);
138 {167 {
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);
140 if (amt != symtab_buffer.len) return error.InputOutput;169 if (amt != symtab_buffer.len) return error.InputOutput;
141 }170 }
142 const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(symtab_buffer.ptr))[0..cmd.nsyms];171 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 {...@@ -154,7 +183,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
154 const buffer = try gpa.alloc(u8, cmd.datasize);183 const buffer = try gpa.alloc(u8, cmd.datasize);
155 defer gpa.free(buffer);184 defer gpa.free(buffer);
156 {185 {
157 const amt = try handle.preadAll(buffer, self.offset + cmd.dataoff);186 const amt = try handle.readPositionalAll(io, buffer, self.offset + cmd.dataoff);
158 if (amt != buffer.len) return error.InputOutput;187 if (amt != buffer.len) return error.InputOutput;
159 }188 }
160 const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry));189 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...@@ -440,12 +469,14 @@ fn initCstringLiterals(self: *Object, allocator: Allocator, file: File.Handle, m
440 const tracy = trace(@src());469 const tracy = trace(@src());
441 defer tracy.end();470 defer tracy.end();
442471
472 const comp = macho_file.base.comp;
473 const io = comp.io;
443 const slice = self.sections.slice();474 const slice = self.sections.slice();
444475
445 for (slice.items(.header), 0..) |sect, n_sect| {476 for (slice.items(.header), 0..) |sect, n_sect| {
446 if (!isCstringLiteral(sect)) continue;477 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));
449 defer allocator.free(data);480 defer allocator.free(data);
450481
451 var count: u32 = 0;482 var count: u32 = 0;
...@@ -628,7 +659,9 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO...@@ -628,7 +659,9 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO
628 const tracy = trace(@src());659 const tracy = trace(@src());
629 defer tracy.end();660 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;
632 const file = macho_file.getFileHandle(self.file_handle);665 const file = macho_file.getFileHandle(self.file_handle);
633666
634 var buffer = std.array_list.Managed(u8).init(gpa);667 var buffer = std.array_list.Managed(u8).init(gpa);
...@@ -647,7 +680,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO...@@ -647,7 +680,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO
647 const slice = self.sections.slice();680 const slice = self.sections.slice();
648 for (slice.items(.header), slice.items(.subsections), 0..) |header, subs, n_sect| {681 for (slice.items(.header), slice.items(.subsections), 0..) |header, subs, n_sect| {
649 if (isCstringLiteral(header) or isFixedSizeLiteral(header)) {682 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));
651 defer gpa.free(data);684 defer gpa.free(data);
652685
653 for (subs.items) |sub| {686 for (subs.items) |sub| {
...@@ -682,7 +715,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO...@@ -682,7 +715,7 @@ pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO
682 buffer.resize(target_size) catch unreachable;715 buffer.resize(target_size) catch unreachable;
683 const gop = try sections_data.getOrPut(target.n_sect);716 const gop = try sections_data.getOrPut(target.n_sect);
684 if (!gop.found_existing) {717 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));
686 }719 }
687 const data = gop.value_ptr.*;720 const data = gop.value_ptr.*;
688 const target_off = try macho_file.cast(usize, target.off);721 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...@@ -1037,9 +1070,11 @@ fn initEhFrameRecords(self: *Object, allocator: Allocator, sect_id: u8, file: Fi
1037 const sect = slice.items(.header)[sect_id];1070 const sect = slice.items(.header)[sect_id];
1038 const relocs = slice.items(.relocs)[sect_id];1071 const relocs = slice.items(.relocs)[sect_id];
10391072
1073 const comp = macho_file.base.comp;
1074 const io = comp.io;
1040 const size = try macho_file.cast(usize, sect.size);1075 const size = try macho_file.cast(usize, sect.size);
1041 try self.eh_frame_data.resize(allocator, size);1076 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);
1043 if (amt != self.eh_frame_data.items.len) return error.InputOutput;1078 if (amt != self.eh_frame_data.items.len) return error.InputOutput;
10441079
1045 // Check for non-personality relocs in FDEs and apply them1080 // 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...@@ -1138,8 +1173,10 @@ fn initUnwindRecords(self: *Object, allocator: Allocator, sect_id: u8, file: Fil
1138 }1173 }
1139 };1174 };
11401175
1176 const comp = macho_file.base.comp;
1177 const io = comp.io;
1141 const header = self.sections.items(.header)[sect_id];1178 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);
1143 defer allocator.free(data);1180 defer allocator.free(data);
11441181
1145 const nrecs = @divExact(data.len, @sizeOf(macho.compact_unwind_entry));1182 const nrecs = @divExact(data.len, @sizeOf(macho.compact_unwind_entry));
...@@ -1348,7 +1385,9 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void {...@@ -1348,7 +1385,9 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void {
1348 const tracy = trace(@src());1385 const tracy = trace(@src());
1349 defer tracy.end();1386 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;
1352 const file = macho_file.getFileHandle(self.file_handle);1391 const file = macho_file.getFileHandle(self.file_handle);
13531392
1354 var dwarf: Dwarf = .{};1393 var dwarf: Dwarf = .{};
...@@ -1358,18 +1397,18 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void {...@@ -1358,18 +1397,18 @@ fn parseDebugInfo(self: *Object, macho_file: *MachO) !void {
1358 const n_sect: u8 = @intCast(index);1397 const n_sect: u8 = @intCast(index);
1359 if (sect.attrs() & macho.S_ATTR_DEBUG == 0) continue;1398 if (sect.attrs() & macho.S_ATTR_DEBUG == 0) continue;
1360 if (mem.eql(u8, sect.sectName(), "__debug_info")) {1399 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);
1362 }1401 }
1363 if (mem.eql(u8, sect.sectName(), "__debug_abbrev")) {1402 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);
1365 }1404 }
1366 if (mem.eql(u8, sect.sectName(), "__debug_str")) {1405 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);
1368 }1407 }
1369 // __debug_str_offs[ets] section is a new addition in DWARFv5 and is generally1408 // __debug_str_offs[ets] section is a new addition in DWARFv5 and is generally
1370 // required in order to correctly parse strings.1409 // required in order to correctly parse strings.
1371 if (mem.eql(u8, sect.sectName(), "__debug_str_offs")) {1410 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);
1373 }1412 }
1374 }1413 }
13751414
...@@ -1611,12 +1650,14 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {...@@ -1611,12 +1650,14 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {
1611 const tracy = trace(@src());1650 const tracy = trace(@src());
1612 defer tracy.end();1651 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;
1615 const handle = macho_file.getFileHandle(self.file_handle);1656 const handle = macho_file.getFileHandle(self.file_handle);
16161657
1617 var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;1658 var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
1618 {1659 {
1619 const amt = try handle.preadAll(&header_buffer, self.offset);1660 const amt = try handle.readPositionalAll(io, &header_buffer, self.offset);
1620 if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;1661 if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;
1621 }1662 }
1622 self.header = @as(*align(1) const macho.mach_header_64, @ptrCast(&header_buffer)).*;1663 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 {...@@ -1637,7 +1678,7 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {
1637 const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds);1678 const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds);
1638 defer gpa.free(lc_buffer);1679 defer gpa.free(lc_buffer);
1639 {1680 {
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));
1641 if (amt != self.header.?.sizeofcmds) return error.InputOutput;1682 if (amt != self.header.?.sizeofcmds) return error.InputOutput;
1642 }1683 }
16431684
...@@ -1647,14 +1688,14 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {...@@ -1647,14 +1688,14 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {
1647 const cmd = lc.cast(macho.symtab_command).?;1688 const cmd = lc.cast(macho.symtab_command).?;
1648 try self.strtab.resize(gpa, cmd.strsize);1689 try self.strtab.resize(gpa, cmd.strsize);
1649 {1690 {
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);
1651 if (amt != self.strtab.items.len) return error.InputOutput;1692 if (amt != self.strtab.items.len) return error.InputOutput;
1652 }1693 }
16531694
1654 const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64));1695 const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64));
1655 defer gpa.free(symtab_buffer);1696 defer gpa.free(symtab_buffer);
1656 {1697 {
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);
1658 if (amt != symtab_buffer.len) return error.InputOutput;1699 if (amt != symtab_buffer.len) return error.InputOutput;
1659 }1700 }
1660 const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(symtab_buffer.ptr))[0..cmd.nsyms];1701 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...@@ -1689,13 +1730,15 @@ pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *M
1689}1730}
16901731
1691pub fn updateArSize(self: *Object, macho_file: *MachO) !void {1732pub fn updateArSize(self: *Object, macho_file: *MachO) !void {
1733 const comp = macho_file.base.comp;
1734 const io = comp.io;
1692 self.output_ar_state.size = if (self.in_archive) |ar| ar.size else size: {1735 self.output_ar_state.size = if (self.in_archive) |ar| ar.size else size: {
1693 const file = macho_file.getFileHandle(self.file_handle);1736 const file = macho_file.getFileHandle(self.file_handle);
1694 break :size (try file.stat()).size;1737 break :size (try file.stat(io)).size;
1695 };1738 };
1696}1739}
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 {
1699 // Header1742 // Header
1700 const size = try macho_file.cast(usize, self.output_ar_state.size);1743 const size = try macho_file.cast(usize, self.output_ar_state.size);
1701 const basename = std.fs.path.basename(self.path);1744 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...@@ -1703,10 +1746,12 @@ pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writ
1703 // Data1746 // Data
1704 const file = macho_file.getFileHandle(self.file_handle);1747 const file = macho_file.getFileHandle(self.file_handle);
1705 // TODO try using copyRangeAll1748 // 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;
1707 const data = try gpa.alloc(u8, size);1752 const data = try gpa.alloc(u8, size);
1708 defer gpa.free(data);1753 defer gpa.free(data);
1709 const amt = try file.preadAll(data, self.offset);1754 const amt = try file.readPositionalAll(io, data, self.offset);
1710 if (amt != size) return error.InputOutput;1755 if (amt != size) return error.InputOutput;
1711 try writer.writeAll(data);1756 try writer.writeAll(data);
1712}1757}
...@@ -1811,7 +1856,9 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void {...@@ -1811,7 +1856,9 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void {
1811 const tracy = trace(@src());1856 const tracy = trace(@src());
1812 defer tracy.end();1857 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;
1815 const headers = self.sections.items(.header);1862 const headers = self.sections.items(.header);
1816 const sections_data = try gpa.alloc([]const u8, headers.len);1863 const sections_data = try gpa.alloc([]const u8, headers.len);
1817 defer {1864 defer {
...@@ -1827,7 +1874,7 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void {...@@ -1827,7 +1874,7 @@ pub fn writeAtoms(self: *Object, macho_file: *MachO) !void {
1827 if (header.isZerofill()) continue;1874 if (header.isZerofill()) continue;
1828 const size = try macho_file.cast(usize, header.size);1875 const size = try macho_file.cast(usize, header.size);
1829 const data = try gpa.alloc(u8, size);1876 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);
1831 if (amt != data.len) return error.InputOutput;1878 if (amt != data.len) return error.InputOutput;
1832 sections_data[n_sect] = data;1879 sections_data[n_sect] = data;
1833 }1880 }
...@@ -1850,7 +1897,9 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void {...@@ -1850,7 +1897,9 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void {
1850 const tracy = trace(@src());1897 const tracy = trace(@src());
1851 defer tracy.end();1898 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;
1854 const headers = self.sections.items(.header);1903 const headers = self.sections.items(.header);
1855 const sections_data = try gpa.alloc([]const u8, headers.len);1904 const sections_data = try gpa.alloc([]const u8, headers.len);
1856 defer {1905 defer {
...@@ -1866,7 +1915,7 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void {...@@ -1866,7 +1915,7 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void {
1866 if (header.isZerofill()) continue;1915 if (header.isZerofill()) continue;
1867 const size = try macho_file.cast(usize, header.size);1916 const size = try macho_file.cast(usize, header.size);
1868 const data = try gpa.alloc(u8, size);1917 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);
1870 if (amt != data.len) return error.InputOutput;1919 if (amt != data.len) return error.InputOutput;
1871 sections_data[n_sect] = data;1920 sections_data[n_sect] = data;
1872 }1921 }
...@@ -2482,11 +2531,11 @@ pub fn getUnwindRecord(self: *Object, index: UnwindInfo.Record.Index) *UnwindInf...@@ -2482,11 +2531,11 @@ pub fn getUnwindRecord(self: *Object, index: UnwindInfo.Record.Index) *UnwindInf
2482}2531}
24832532
2484/// Caller owns the memory.2533/// 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 {
2486 const header = self.sections.items(.header)[n_sect];2535 const header = self.sections.items(.header)[n_sect];
2487 const size = math.cast(usize, header.size) orelse return error.Overflow;2536 const size = math.cast(usize, header.size) orelse return error.Overflow;
2488 const data = try allocator.alloc(u8, size);2537 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);
2490 errdefer allocator.free(data);2539 errdefer allocator.free(data);
2491 if (amt != data.len) return error.InputOutput;2540 if (amt != data.len) return error.InputOutput;
2492 return data;2541 return data;
...@@ -2710,15 +2759,17 @@ const x86_64 = struct {...@@ -2710,15 +2759,17 @@ const x86_64 = struct {
2710 handle: File.Handle,2759 handle: File.Handle,
2711 macho_file: *MachO,2760 macho_file: *MachO,
2712 ) !void {2761 ) !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
2715 const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info));2766 const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info));
2716 defer gpa.free(relocs_buffer);2767 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);
2718 if (amt != relocs_buffer.len) return error.InputOutput;2769 if (amt != relocs_buffer.len) return error.InputOutput;
2719 const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(relocs_buffer.ptr))[0..sect.nreloc];2770 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);
2722 defer gpa.free(code);2773 defer gpa.free(code);
27232774
2724 try out.ensureTotalCapacityPrecise(gpa, relocs.len);2775 try out.ensureTotalCapacityPrecise(gpa, relocs.len);
...@@ -2877,15 +2928,17 @@ const aarch64 = struct {...@@ -2877,15 +2928,17 @@ const aarch64 = struct {
2877 handle: File.Handle,2928 handle: File.Handle,
2878 macho_file: *MachO,2929 macho_file: *MachO,
2879 ) !void {2930 ) !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
2882 const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info));2935 const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info));
2883 defer gpa.free(relocs_buffer);2936 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);
2885 if (amt != relocs_buffer.len) return error.InputOutput;2938 if (amt != relocs_buffer.len) return error.InputOutput;
2886 const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(relocs_buffer.ptr))[0..sect.nreloc];2939 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);
2889 defer gpa.free(code);2942 defer gpa.free(code);
28902943
2891 try out.ensureTotalCapacityPrecise(gpa, relocs.len);2944 try out.ensureTotalCapacityPrecise(gpa, relocs.len);
...@@ -3061,27 +3114,3 @@ const aarch64 = struct {...@@ -3061,27 +3114,3 @@ const aarch64 = struct {
3061 }3114 }
3062 }3115 }
3063};3116};
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...@@ -171,6 +171,9 @@ pub fn getAtomData(self: ZigObject, macho_file: *MachO, atom: Atom, buffer: []u8
171 const isec = atom.getInputSection(macho_file);171 const isec = atom.getInputSection(macho_file);
172 assert(!isec.isZerofill());172 assert(!isec.isZerofill());
173173
174 const comp = macho_file.base.comp;
175 const io = comp.io;
176
174 switch (isec.type()) {177 switch (isec.type()) {
175 macho.S_THREAD_LOCAL_REGULAR => {178 macho.S_THREAD_LOCAL_REGULAR => {
176 const tlv = self.tlv_initializers.get(atom.atom_index).?;179 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...@@ -182,7 +185,7 @@ pub fn getAtomData(self: ZigObject, macho_file: *MachO, atom: Atom, buffer: []u8
182 else => {185 else => {
183 const sect = macho_file.sections.items(.header)[atom.out_n_sect];186 const sect = macho_file.sections.items(.header)[atom.out_n_sect];
184 const file_offset = sect.offset + atom.value;187 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);
186 if (amt != buffer.len) return error.InputOutput;189 if (amt != buffer.len) return error.InputOutput;
187 },190 },
188 }191 }
...@@ -290,12 +293,14 @@ pub fn dedupLiterals(self: *ZigObject, lp: MachO.LiteralPool, macho_file: *MachO...@@ -290,12 +293,14 @@ pub fn dedupLiterals(self: *ZigObject, lp: MachO.LiteralPool, macho_file: *MachO
290/// We need this so that we can write to an archive.293/// We need this so that we can write to an archive.
291/// TODO implement writing ZigObject data directly to a buffer instead.294/// TODO implement writing ZigObject data directly to a buffer instead.
292pub fn readFileContents(self: *ZigObject, macho_file: *MachO) !void {295pub 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;
294 // Size of the output object file is always the offset + size of the strtab300 // Size of the output object file is always the offset + size of the strtab
295 const size = macho_file.symtab_cmd.stroff + macho_file.symtab_cmd.strsize;301 const size = macho_file.symtab_cmd.stroff + macho_file.symtab_cmd.strsize;
296 const gpa = macho_file.base.comp.gpa;
297 try self.data.resize(gpa, size);302 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|
299 return diags.fail("failed to read output file: {s}", .{@errorName(err)});304 return diags.fail("failed to read output file: {s}", .{@errorName(err)});
300 if (amt != size)305 if (amt != size)
301 return diags.fail("unexpected EOF reading from output file", .{});306 return diags.fail("unexpected EOF reading from output file", .{});
...@@ -945,6 +950,8 @@ fn updateNavCode(...@@ -945,6 +950,8 @@ fn updateNavCode(
945) link.File.UpdateNavError!void {950) link.File.UpdateNavError!void {
946 const zcu = pt.zcu;951 const zcu = pt.zcu;
947 const gpa = zcu.gpa;952 const gpa = zcu.gpa;
953 const comp = zcu.comp;
954 const io = comp.io;
948 const ip = &zcu.intern_pool;955 const ip = &zcu.intern_pool;
949 const nav = ip.getNav(nav_index);956 const nav = ip.getNav(nav_index);
950957
...@@ -1012,8 +1019,8 @@ fn updateNavCode(...@@ -1012,8 +1019,8 @@ fn updateNavCode(
10121019
1013 if (!sect.isZerofill()) {1020 if (!sect.isZerofill()) {
1014 const file_offset = sect.offset + atom.value;1021 const file_offset = sect.offset + atom.value;
1015 macho_file.base.file.?.pwriteAll(code, file_offset) catch |err|1022 macho_file.base.file.?.writePositionalAll(io, code, file_offset) catch |err|
1016 return macho_file.base.cgFail(nav_index, "failed to write output file: {s}", .{@errorName(err)});1023 return macho_file.base.cgFail(nav_index, "failed to write output file: {t}", .{err});
1017 }1024 }
1018}1025}
10191026
...@@ -1493,7 +1500,7 @@ fn writeTrampoline(tr_sym: Symbol, target: Symbol, macho_file: *MachO) !void {...@@ -1493,7 +1500,7 @@ fn writeTrampoline(tr_sym: Symbol, target: Symbol, macho_file: *MachO) !void {
1493 .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf),1500 .x86_64 => try x86_64.writeTrampolineCode(source_addr, target_addr, &buf),
1494 else => @panic("TODO implement write trampoline for this CPU arch"),1501 else => @panic("TODO implement write trampoline for this CPU arch"),
1495 };1502 };
1496 try macho_file.base.file.?.pwriteAll(out, fileoff);1503 return macho_file.pwriteAll(out, fileoff);
1497}1504}
14981505
1499pub fn getOrCreateMetadataForNav(1506pub fn getOrCreateMetadataForNav(
src/link/MachO/fat.zig+10-8
...@@ -1,20 +1,22 @@...@@ -1,20 +1,22 @@
1const builtin = @import("builtin");
2const native_endian = builtin.target.cpu.arch.endian();
3
1const std = @import("std");4const std = @import("std");
5const Io = std.Io;
2const assert = std.debug.assert;6const assert = std.debug.assert;
3const builtin = @import("builtin");
4const log = std.log.scoped(.macho);7const log = std.log.scoped(.macho);
5const macho = std.macho;8const macho = std.macho;
6const mem = std.mem;9const mem = std.mem;
7const native_endian = builtin.target.cpu.arch.endian();
810
9const MachO = @import("../MachO.zig");11const MachO = @import("../MachO.zig");
1012
11pub fn readFatHeader(file: std.fs.File) !macho.fat_header {13pub fn readFatHeader(io: Io, file: Io.File) !macho.fat_header {
12 return readFatHeaderGeneric(macho.fat_header, file, 0);14 return readFatHeaderGeneric(io, macho.fat_header, file, 0);
13}15}
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 {
16 var buffer: [@sizeOf(Hdr)]u8 = undefined;18 var buffer: [@sizeOf(Hdr)]u8 = undefined;
17 const nread = try file.preadAll(&buffer, offset);19 const nread = try file.readPositionalAll(io, &buffer, offset);
18 if (nread != buffer.len) return error.InputOutput;20 if (nread != buffer.len) return error.InputOutput;
19 var hdr = @as(*align(1) const Hdr, @ptrCast(&buffer)).*;21 var hdr = @as(*align(1) const Hdr, @ptrCast(&buffer)).*;
20 mem.byteSwapAllFields(Hdr, &hdr);22 mem.byteSwapAllFields(Hdr, &hdr);
...@@ -27,12 +29,12 @@ pub const Arch = struct {...@@ -27,12 +29,12 @@ pub const Arch = struct {
27 size: u32,29 size: u32,
28};30};
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 {
31 var count: usize = 0;33 var count: usize = 0;
32 var fat_arch_index: u32 = 0;34 var fat_arch_index: u32 = 0;
33 while (fat_arch_index < fat_header.nfat_arch and count < out.len) : (fat_arch_index += 1) {35 while (fat_arch_index < fat_header.nfat_arch and count < out.len) : (fat_arch_index += 1) {
34 const offset = @sizeOf(macho.fat_header) + @sizeOf(macho.fat_arch) * fat_arch_index;36 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);
36 // If we come across an architecture that we do not know how to handle, that's38 // If we come across an architecture that we do not know how to handle, that's
37 // fine because we can keep looking for one that might match.39 // fine because we can keep looking for one that might match.
38 const arch: std.Target.Cpu.Arch = switch (fat_arch.cputype) {40 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) {...@@ -355,11 +355,12 @@ pub const File = union(enum) {
355 dylib: Dylib,355 dylib: Dylib,
356 };356 };
357357
358 pub const Handle = std.fs.File;358 pub const Handle = Io.File;
359 pub const HandleIndex = Index;359 pub const HandleIndex = Index;
360};360};
361361
362const std = @import("std");362const std = @import("std");
363const Io = std.Io;
363const assert = std.debug.assert;364const assert = std.debug.assert;
364const log = std.log.scoped(.link);365const log = std.log.scoped(.link);
365const macho = std.macho;366const macho = std.macho;
src/link/MachO/hasher.zig+20-28
...@@ -1,34 +1,36 @@...@@ -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
1pub fn ParallelHasher(comptime Hasher: type) type {8pub fn ParallelHasher(comptime Hasher: type) type {
2 const hash_size = Hasher.digest_length;9 const hash_size = Hasher.digest_length;
310
4 return struct {11 return struct {
5 allocator: Allocator,12 pub fn hash(gpa: Allocator, io: Io, file: Io.File, out: [][hash_size]u8, opts: struct {
6 io: std.Io,
7
8 pub fn hash(self: Self, file: fs.File, out: [][hash_size]u8, opts: struct {
9 chunk_size: u64 = 0x4000,13 chunk_size: u64 = 0x4000,
10 max_file_size: ?u64 = null,14 max_file_size: ?u64 = null,
11 }) !void {15 }) !void {
12 const tracy = trace(@src());16 const tracy = trace(@src());
13 defer tracy.end();17 defer tracy.end();
1418
15 const io = self.io;
16
17 const file_size = blk: {19 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);
19 break :blk std.math.cast(usize, file_size) orelse return error.Overflow;21 break :blk std.math.cast(usize, file_size) orelse return error.Overflow;
20 };22 };
21 const chunk_size = std.math.cast(usize, opts.chunk_size) orelse return error.Overflow;23 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);25 const buffer = try gpa.alloc(u8, chunk_size * out.len);
24 defer self.allocator.free(buffer);26 defer gpa.free(buffer);
2527
26 const results = try self.allocator.alloc(fs.File.PReadError!usize, out.len);28 const results = try gpa.alloc(Io.File.ReadPositionalError!usize, out.len);
27 defer self.allocator.free(results);29 defer gpa.free(results);
2830
29 {31 {
30 var group: std.Io.Group = .init;32 var group: Io.Group = .init;
31 errdefer group.cancel(io);33 defer group.cancel(io);
3234
33 for (out, results, 0..) |*out_buf, *result, i| {35 for (out, results, 0..) |*out_buf, *result, i| {
34 const fstart = i * chunk_size;36 const fstart = i * chunk_size;
...@@ -37,6 +39,7 @@ pub fn ParallelHasher(comptime Hasher: type) type {...@@ -37,6 +39,7 @@ pub fn ParallelHasher(comptime Hasher: type) type {
37 else39 else
38 chunk_size;40 chunk_size;
39 group.async(io, worker, .{41 group.async(io, worker, .{
42 io,
40 file,43 file,
41 fstart,44 fstart,
42 buffer[fstart..][0..fsize],45 buffer[fstart..][0..fsize],
...@@ -51,26 +54,15 @@ pub fn ParallelHasher(comptime Hasher: type) type {...@@ -51,26 +54,15 @@ pub fn ParallelHasher(comptime Hasher: type) type {
51 }54 }
5255
53 fn worker(56 fn worker(
54 file: fs.File,57 io: Io,
58 file: Io.File,
55 fstart: usize,59 fstart: usize,
56 buffer: []u8,60 buffer: []u8,
57 out: *[hash_size]u8,61 out: *[hash_size]u8,
58 err: *fs.File.PReadError!usize,62 err: *Io.File.ReadPositionalError!usize,
59 ) void {63 ) void {
60 const tracy = trace(@src());64 err.* = file.readPositionalAll(io, buffer, fstart);
61 defer tracy.end();
62 err.* = file.preadAll(buffer, fstart);
63 Hasher.hash(buffer, out, .{});65 Hasher.hash(buffer, out, .{});
64 }66 }
65
66 const Self = @This();
67 };67 };
68}68}
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 @@...@@ -1,6 +1,7 @@
1pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void {1pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void {
2 const gpa = macho_file.base.comp.gpa;2 const gpa = comp.gpa;
3 const diags = &macho_file.base.comp.link_diags;3 const io = comp.io;
4 const diags = &comp.link_diags;
45
5 // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list.6 // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list.
6 var positionals = std.array_list.Managed(link.Input).init(gpa);7 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...@@ -9,24 +10,22 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
9 positionals.appendSliceAssumeCapacity(comp.link_inputs);10 positionals.appendSliceAssumeCapacity(comp.link_inputs);
1011
11 for (comp.c_object_table.keys()) |key| {12 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));
13 }14 }
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
17 if (macho_file.getZigObject() == null and positionals.items.len == 1) {18 if (macho_file.getZigObject() == null and positionals.items.len == 1) {
18 // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all19 // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all
19 // debug info segments/sections (this is apparently by design by Apple), we copy20 // debug info segments/sections (this is apparently by design by Apple), we copy
20 // the *only* input file over.21 // the *only* input file over.
21 const path = positionals.items[0].path().?;22 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|
23 return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) });24 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|
25 return diags.fail("failed to stat {f}: {s}", .{ path, @errorName(err) });26 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 link.File.copyRangeAll2(io, in_file, macho_file.base.file.?, 0, 0, stat.size) catch |err|
27 return diags.fail("failed to copy range of file {f}: {s}", .{ path, @errorName(err) });28 return diags.fail("failed to copy range of file {f}: {t}", .{ path, err });
28 if (amt != stat.size)
29 return diags.fail("unexpected short write in copy range of file {f}", .{path});
30 return;29 return;
31 }30 }
3231
...@@ -79,6 +78,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat...@@ -79,6 +78,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
7978
80pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void {79pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.File.FlushError!void {
81 const gpa = comp.gpa;80 const gpa = comp.gpa;
81 const io = comp.io;
82 const diags = &macho_file.base.comp.link_diags;82 const diags = &macho_file.base.comp.link_diags;
8383
84 var positionals = std.array_list.Managed(link.Input).init(gpa);84 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: ?...@@ -88,17 +88,17 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
88 positionals.appendSliceAssumeCapacity(comp.link_inputs);88 positionals.appendSliceAssumeCapacity(comp.link_inputs);
8989
90 for (comp.c_object_table.keys()) |key| {90 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));
92 }92 }
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
96 if (comp.compiler_rt_strat == .obj) {96 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));
98 }98 }
9999
100 if (comp.ubsan_rt_strat == .obj) {100 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));
102 }102 }
103103
104 for (positionals.items) |link_input| {104 for (positionals.items) |link_input| {
...@@ -229,7 +229,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?...@@ -229,7 +229,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
229229
230 assert(writer.end == total_size);230 assert(writer.end == total_size);
231231
232 try macho_file.setEndPos(total_size);232 try macho_file.setLength(total_size);
233 try macho_file.pwriteAll(writer.buffered(), 0);233 try macho_file.pwriteAll(writer.buffered(), 0);
234234
235 if (diags.hasErrors()) return error.LinkFailure;235 if (diags.hasErrors()) return error.LinkFailure;
src/link/MachO/uuid.zig+17-16
...@@ -1,28 +1,38 @@...@@ -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
1/// Calculates Md5 hash of each chunk in parallel and then hashes all Md5 hashes to produce9/// Calculates Md5 hash of each chunk in parallel and then hashes all Md5 hashes to produce
2/// the final digest.10/// the final digest.
3/// While this is NOT a correct MD5 hash of the contents, this methodology is used by LLVM/LLD11/// While this is NOT a correct MD5 hash of the contents, this methodology is used by LLVM/LLD
4/// and we will use it too as it seems accepted by Apple OSes.12/// and we will use it too as it seems accepted by Apple OSes.
5/// TODO LLD also hashes the output filename to disambiguate between same builds with different13/// TODO LLD also hashes the output filename to disambiguate between same builds with different
6/// output files. Should we also do that?14/// 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 {
8 const tracy = trace(@src());16 const tracy = trace(@src());
9 defer tracy.end();17 defer tracy.end();
1018
19 const gpa = comp.gpa;
20 const io = comp.io;
21
11 const chunk_size: usize = 1024 * 1024;22 const chunk_size: usize = 1024 * 1024;
12 const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow;23 const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow;
13 const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks;24 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);26 const hashes = try gpa.alloc([Md5.digest_length]u8, actual_num_chunks);
16 defer comp.gpa.free(hashes);27 defer gpa.free(hashes);
1728
18 var hasher = Hasher(Md5){ .allocator = comp.gpa, .io = comp.io };29 try ParallelHasher(Md5).hash(gpa, io, file, hashes, .{
19 try hasher.hash(file, hashes, .{
20 .chunk_size = chunk_size,30 .chunk_size = chunk_size,
21 .max_file_size = file_size,31 .max_file_size = file_size,
22 });32 });
2333
24 const final_buffer = try comp.gpa.alloc(u8, actual_num_chunks * Md5.digest_length);34 const final_buffer = try gpa.alloc(u8, actual_num_chunks * Md5.digest_length);
25 defer comp.gpa.free(final_buffer);35 defer gpa.free(final_buffer);
2636
27 for (hashes, 0..) |hash, i| {37 for (hashes, 0..) |hash, i| {
28 @memcpy(final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash);38 @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 {...@@ -37,12 +47,3 @@ inline fn conform(out: *[Md5.digest_length]u8) void {
37 out[6] = (out[6] & 0x0F) | (3 << 4);47 out[6] = (out[6] & 0x0F) | (3 << 4);
38 out[8] = (out[8] & 0x3F) | 0x80;48 out[8] = (out[8] & 0x3F) | 0x80;
39}49}
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,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,
1file: std.Io.File,15file: std.Io.File,
2flags: packed struct {16flags: packed struct {
3 block_size: std.mem.Alignment,17 block_size: std.mem.Alignment,
...@@ -16,16 +30,22 @@ writers: std.SinglyLinkedList,...@@ -16,16 +30,22 @@ writers: std.SinglyLinkedList,
1630
17pub const growth_factor = 4;31pub 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{
20 NotFile,34 NotFile,
21 SystemResources,35 SystemResources,
22 IsDir,36 IsDir,
23 Unseekable,37 Unseekable,
24 NoSpaceLeft,38 NoSpaceLeft,
39
40 InputOutput,
41 FileTooBig,
42 FileBusy,
43 NonResizable,
25};44};
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 {
28 var mf: MappedFile = .{47 var mf: MappedFile = .{
48 .io = io,
29 .file = file,49 .file = file,
30 .flags = undefined,50 .flags = undefined,
31 .section = if (is_windows) windows.INVALID_HANDLE_VALUE else {},51 .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 {...@@ -55,18 +75,41 @@ pub fn init(file: std.Io.File, gpa: std.mem.Allocator) !MappedFile {
55 };75 };
56 }76 }
57 if (is_linux) {77 if (is_linux) {
58 const statx = try linux.wrapped.statx(78 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
59 mf.file.handle,79 .{ .major = 30, .minor = 0, .patch = 0 }
60 "",80 else
61 std.posix.AT.EMPTY_PATH,81 .{ .major = 2, .minor = 28, .patch = 0 });
62 .{ .TYPE = true, .SIZE = true, .BLOCKS = true },82 const sys = if (use_c) std.c else std.os.linux;
63 );83 while (true) {
64 assert(statx.mask.TYPE);84 var statx = std.mem.zeroes(linux.Statx);
65 assert(statx.mask.SIZE);85 const rc = sys.statx(
66 assert(statx.mask.BLOCKS);86 mf.file.handle,
6787 "",
68 if (!std.posix.S.ISREG(statx.mode)) return error.PathAlreadyExists;88 std.posix.AT.EMPTY_PATH,
69 break :stat .{ statx.size, @max(std.heap.pageSize(), statx.blksize) };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 }
70 }113 }
71 const stat = try std.posix.fstat(mf.file.handle);114 const stat = try std.posix.fstat(mf.file.handle);
72 if (!std.posix.S.ISREG(stat.mode)) return error.PathAlreadyExists;115 if (!std.posix.S.ISREG(stat.mode)) return error.PathAlreadyExists;
...@@ -433,8 +476,8 @@ pub const Node = extern struct {...@@ -433,8 +476,8 @@ pub const Node = extern struct {
433 return n;476 return n;
434 },477 },
435 .streaming,478 .streaming,
436 .streaming_reading,479 .streaming_simple,
437 .positional_reading,480 .positional_simple,
438 .failure,481 .failure,
439 => {482 => {
440 const dest = limit.slice(interface.unusedCapacitySlice());483 const dest = limit.slice(interface.unusedCapacitySlice());
...@@ -612,13 +655,14 @@ pub fn addNodeAfter(...@@ -612,13 +655,14 @@ pub fn addNodeAfter(
612}655}
613656
614fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested_size: u64) !void {657fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested_size: u64) !void {
658 const io = mf.io;
615 const node = ni.get(mf);659 const node = ni.get(mf);
616 const old_offset, const old_size = node.location().resolve(mf);660 const old_offset, const old_size = node.location().resolve(mf);
617 const new_size = node.flags.alignment.forward(@intCast(requested_size));661 const new_size = node.flags.alignment.forward(@intCast(requested_size));
618 // Resize the entire file662 // Resize the entire file
619 if (ni == Node.Index.root) {663 if (ni == Node.Index.root) {
620 try mf.ensureCapacityForSetLocation(gpa);664 try mf.ensureCapacityForSetLocation(gpa);
621 try std.fs.File.adaptFromNewApi(mf.file).setEndPos(new_size);665 try mf.file.setLength(io, new_size);
622 try mf.ensureTotalCapacity(@intCast(new_size));666 try mf.ensureTotalCapacity(@intCast(new_size));
623 ni.setLocationAssumeCapacity(mf, old_offset, new_size);667 ni.setLocationAssumeCapacity(mf, old_offset, new_size);
624 return;668 return;
...@@ -1059,12 +1103,3 @@ fn verifyNode(mf: *MappedFile, parent_ni: Node.Index) void {...@@ -1059,12 +1103,3 @@ fn verifyNode(mf: *MappedFile, parent_ni: Node.Index) void {
1059 ni = node.next;1103 ni = node.next;
1060 }1104 }
1061}1105}
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(...@@ -121,7 +121,7 @@ pub fn enqueueZcu(
121 link.doZcuTask(comp, tid, task);121 link.doZcuTask(comp, tid, task);
122}122}
123123
124pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) void {124pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) Io.Cancelable!void {
125 if (q.future != null) {125 if (q.future != null) {
126 q.prelink_queue.close(comp.io);126 q.prelink_queue.close(comp.io);
127 return;127 return;
...@@ -136,6 +136,7 @@ pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) void {...@@ -136,6 +136,7 @@ pub fn finishPrelinkQueue(q: *Queue, comp: *Compilation) void {
136 } else |err| switch (err) {136 } else |err| switch (err) {
137 error.OutOfMemory => comp.link_diags.setAllocFailure(),137 error.OutOfMemory => comp.link_diags.setAllocFailure(),
138 error.LinkFailure => {},138 error.LinkFailure => {},
139 error.Canceled => |e| return e,
139 }140 }
140 }141 }
141}142}
...@@ -175,6 +176,7 @@ fn runLinkTasks(q: *Queue, comp: *Compilation) void {...@@ -175,6 +176,7 @@ fn runLinkTasks(q: *Queue, comp: *Compilation) void {
175 lf.post_prelink = true;176 lf.post_prelink = true;
176 } else |err| switch (err) {177 } else |err| switch (err) {
177 error.OutOfMemory => comp.link_diags.setAllocFailure(),178 error.OutOfMemory => comp.link_diags.setAllocFailure(),
179 error.Canceled => @panic("TODO"),
178 error.LinkFailure => {},180 error.LinkFailure => {},
179 }181 }
180 }182 }
src/link/SpirV.zig+5-3
...@@ -33,6 +33,7 @@ pub fn createEmpty(...@@ -33,6 +33,7 @@ pub fn createEmpty(
33 options: link.File.OpenOptions,33 options: link.File.OpenOptions,
34) !*Linker {34) !*Linker {
35 const gpa = comp.gpa;35 const gpa = comp.gpa;
36 const io = comp.io;
36 const target = &comp.root_mod.resolved_target.result;37 const target = &comp.root_mod.resolved_target.result;
3738
38 assert(!comp.config.use_lld); // Caught by Compilation.Config.resolve39 assert(!comp.config.use_lld); // Caught by Compilation.Config.resolve
...@@ -78,7 +79,7 @@ pub fn createEmpty(...@@ -78,7 +79,7 @@ pub fn createEmpty(
78 };79 };
79 errdefer linker.deinit();80 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, .{
82 .truncate = true,83 .truncate = true,
83 .read = true,84 .read = true,
84 });85 });
...@@ -245,6 +246,7 @@ pub fn flush(...@@ -245,6 +246,7 @@ pub fn flush(
245 const comp = linker.base.comp;246 const comp = linker.base.comp;
246 const diags = &comp.link_diags;247 const diags = &comp.link_diags;
247 const gpa = comp.gpa;248 const gpa = comp.gpa;
249 const io = comp.io;
248250
249 // We need to export the list of error names somewhere so that we can pretty-print them in the251 // We need to export the list of error names somewhere so that we can pretty-print them in the
250 // executor. This is not really an important thing though, so we can just dump it in any old252 // 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(...@@ -286,8 +288,8 @@ pub fn flush(
286 };288 };
287289
288 // TODO endianness bug. use file writer and call writeSliceEndian instead290 // TODO endianness bug. use file writer and call writeSliceEndian instead
289 linker.base.file.?.writeAll(@ptrCast(linked_module)) catch |err|291 linker.base.file.?.writeStreamingAll(io, @ptrCast(linked_module)) catch |err|
290 return diags.fail("failed to write: {s}", .{@errorName(err)});292 return diags.fail("failed to write: {t}", .{err});
291}293}
292294
293fn linkModule(arena: Allocator, module: []Word, progress: std.Progress.Node) ![]Word {295fn 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();...@@ -20,6 +20,7 @@ const native_endian = builtin.cpu.arch.endian();
20const build_options = @import("build_options");20const build_options = @import("build_options");
2121
22const std = @import("std");22const std = @import("std");
23const Io = std.Io;
23const Allocator = std.mem.Allocator;24const Allocator = std.mem.Allocator;
24const Cache = std.Build.Cache;25const Cache = std.Build.Cache;
25const Path = Cache.Path;26const Path = Cache.Path;
...@@ -428,7 +429,11 @@ pub const OutputFunctionIndex = enum(u32) {...@@ -428,7 +429,11 @@ pub const OutputFunctionIndex = enum(u32) {
428429
429 pub fn fromSymbolName(wasm: *const Wasm, name: String) OutputFunctionIndex {430 pub fn fromSymbolName(wasm: *const Wasm, name: String) OutputFunctionIndex {
430 if (wasm.flush_buffer.function_imports.getIndex(name)) |i| return @enumFromInt(i);431 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 });
432 }437 }
433};438};
434439
...@@ -2996,16 +3001,18 @@ pub fn createEmpty(...@@ -2996,16 +3001,18 @@ pub fn createEmpty(
2996 .named => |name| (try wasm.internString(name)).toOptional(),3001 .named => |name| (try wasm.internString(name)).toOptional(),
2997 };3002 };
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, .{
3000 .truncate = true,3007 .truncate = true,
3001 .read = true,3008 .read = true,
3002 .mode = if (fs.has_executable_bit)3009 .permissions = if (Io.File.Permissions.has_executable_bit)
3003 if (target.os.tag == .wasi and output_mode == .Exe)3010 if (target.os.tag == .wasi and output_mode == .Exe)
3004 fs.File.default_mode | 0b001_000_0003011 .executable_file
3005 else3012 else
3006 fs.File.default_mode3013 .default_file
3007 else3014 else
3008 0,3015 .default_file,
3009 });3016 });
3010 wasm.name = emit.sub_path;3017 wasm.name = emit.sub_path;
30113018
...@@ -3013,14 +3020,16 @@ pub fn createEmpty(...@@ -3013,14 +3020,16 @@ pub fn createEmpty(
3013}3020}
30143021
3015fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void {3022fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void {
3016 const diags = &wasm.base.comp.link_diags;3023 const comp = wasm.base.comp;
3017 const obj = link.openObject(path, false, false) catch |err| {3024 const io = comp.io;
3018 switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) {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})) {
3019 error.LinkFailure => return,3028 error.LinkFailure => return,
3020 }3029 }
3021 };3030 };
3022 wasm.parseObject(obj) catch |err| {3031 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})) {
3024 error.LinkFailure => return,3033 error.LinkFailure => return,
3025 }3034 }
3026 };3035 };
...@@ -3032,7 +3041,7 @@ fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {...@@ -3032,7 +3041,7 @@ fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {
3032 const io = wasm.base.comp.io;3041 const io = wasm.base.comp.io;
3033 const gc_sections = wasm.base.gc_sections;3042 const gc_sections = wasm.base.gc_sections;
30343043
3035 defer obj.file.close();3044 defer obj.file.close(io);
30363045
3037 var file_reader = obj.file.reader(io, &.{});3046 var file_reader = obj.file.reader(io, &.{});
30383047
...@@ -3060,7 +3069,7 @@ fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {...@@ -3060,7 +3069,7 @@ fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {
3060 const io = wasm.base.comp.io;3069 const io = wasm.base.comp.io;
3061 const gc_sections = wasm.base.gc_sections;3070 const gc_sections = wasm.base.gc_sections;
30623071
3063 defer obj.file.close();3072 defer obj.file.close(io);
30643073
3065 var file_reader = obj.file.reader(io, &.{});3074 var file_reader = obj.file.reader(io, &.{});
30663075
...@@ -3529,7 +3538,10 @@ pub fn markFunctionImport(...@@ -3529,7 +3538,10 @@ pub fn markFunctionImport(
3529 import: *FunctionImport,3538 import: *FunctionImport,
3530 func_index: FunctionImport.Index,3539 func_index: FunctionImport.Index,
3531) link.File.FlushError!void {3540) 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.
3533 import.flags.alive = true;3545 import.flags.alive = true;
35343546
3535 const comp = wasm.base.comp;3547 const comp = wasm.base.comp;
...@@ -3549,8 +3561,9 @@ pub fn markFunctionImport(...@@ -3549,8 +3561,9 @@ pub fn markFunctionImport(
3549 } else {3561 } else {
3550 try wasm.function_imports.put(gpa, name, .fromObject(func_index, wasm));3562 try wasm.function_imports.put(gpa, name, .fromObject(func_index, wasm));
3551 }3563 }
3552 } else {3564 } else switch (import.resolution.unpack(wasm)) {
3553 try markFunction(wasm, import.resolution.unpack(wasm).object_function, import.flags.exported);3565 .object_function => try markFunction(wasm, import.resolution.unpack(wasm).object_function, import.flags.exported),
3566 else => return,
3554 }3567 }
3555}3568}
35563569
...@@ -3589,7 +3602,10 @@ fn markGlobalImport(...@@ -3589,7 +3602,10 @@ fn markGlobalImport(
3589 import: *GlobalImport,3602 import: *GlobalImport,
3590 global_index: GlobalImport.Index,3603 global_index: GlobalImport.Index,
3591) link.File.FlushError!void {3604) 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.
3593 import.flags.alive = true;3609 import.flags.alive = true;
35943610
3595 const comp = wasm.base.comp;3611 const comp = wasm.base.comp;
...@@ -3619,8 +3635,9 @@ fn markGlobalImport(...@@ -3619,8 +3635,9 @@ fn markGlobalImport(
3619 } else {3635 } else {
3620 try wasm.global_imports.put(gpa, name, .fromObject(global_index, wasm));3636 try wasm.global_imports.put(gpa, name, .fromObject(global_index, wasm));
3621 }3637 }
3622 } else {3638 } else switch (import.resolution.unpack(wasm)) {
3623 try markGlobal(wasm, import.resolution.unpack(wasm).object_global, import.flags.exported);3639 .object_global => try markGlobal(wasm, import.resolution.unpack(wasm).object_global, import.flags.exported),
3640 else => return,
3624 }3641 }
3625}3642}
36263643
...@@ -3823,8 +3840,9 @@ pub fn flush(...@@ -3823,8 +3840,9 @@ pub fn flush(
3823 const comp = wasm.base.comp;3840 const comp = wasm.base.comp;
3824 const diags = &comp.link_diags;3841 const diags = &comp.link_diags;
3825 const gpa = comp.gpa;3842 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
3829 if (wasm.base.zcu_object_basename) |raw| {3847 if (wasm.base.zcu_object_basename) |raw| {
3830 const zcu_obj_path: Path = try comp.resolveEmitPathFlush(arena, .temp, raw);3848 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...@@ -4037,7 +4055,7 @@ pub fn tagNameSymbolIndex(wasm: *Wasm, ip_index: InternPool.Index) Allocator.Err
4037 const comp = wasm.base.comp;4055 const comp = wasm.base.comp;
4038 assert(comp.config.output_mode == .Obj);4056 assert(comp.config.output_mode == .Obj);
4039 const gpa = comp.gpa;4057 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});
4041 const gop = try wasm.symbol_table.getOrPut(gpa, name);4059 const gop = try wasm.symbol_table.getOrPut(gpa, name);
4042 gop.value_ptr.* = {};4060 gop.value_ptr.* = {};
4043 return @enumFromInt(gop.index);4061 return @enumFromInt(gop.index);
src/link/Wasm/Flush.zig+9-5
...@@ -108,6 +108,7 @@ pub fn deinit(f: *Flush, gpa: Allocator) void {...@@ -108,6 +108,7 @@ pub fn deinit(f: *Flush, gpa: Allocator) void {
108108
109pub fn finish(f: *Flush, wasm: *Wasm) !void {109pub fn finish(f: *Flush, wasm: *Wasm) !void {
110 const comp = wasm.base.comp;110 const comp = wasm.base.comp;
111 const io = comp.io;
111 const shared_memory = comp.config.shared_memory;112 const shared_memory = comp.config.shared_memory;
112 const diags = &comp.link_diags;113 const diags = &comp.link_diags;
113 const gpa = comp.gpa;114 const gpa = comp.gpa;
...@@ -127,17 +128,20 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {...@@ -127,17 +128,20 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
127 if (comp.zcu) |zcu| {128 if (comp.zcu) |zcu| {
128 const ip: *const InternPool = &zcu.intern_pool; // No mutations allowed!129 const ip: *const InternPool = &zcu.intern_pool; // No mutations allowed!
129130
131 log.debug("total MIR instructions: {d}", .{wasm.mir_instructions.len});
132
130 // Detect any intrinsics that were called; they need to have dependencies on the symbols marked.133 // Detect any intrinsics that were called; they need to have dependencies on the symbols marked.
131 // Likewise detect `@tagName` calls so those functions can be included in the output and synthesized.134 // Likewise detect `@tagName` calls so those functions can be included in the output and synthesized.
132 for (wasm.mir_instructions.items(.tag), wasm.mir_instructions.items(.data)) |tag, *data| switch (tag) {135 for (wasm.mir_instructions.items(.tag), wasm.mir_instructions.items(.data)) |tag, *data| switch (tag) {
133 .call_intrinsic => {136 .call_intrinsic => {
134 const symbol_name = try wasm.internString(@tagName(data.intrinsic));137 const symbol_name = try wasm.internString(@tagName(data.intrinsic));
135 const i: Wasm.FunctionImport.Index = @enumFromInt(wasm.object_function_imports.getIndex(symbol_name) orelse {138 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)", .{139 return diags.fail("missing compiler runtime intrinsic '{t}' (undefined linker symbol)", .{
137 @tagName(data.intrinsic),140 data.intrinsic,
138 });141 });
139 });142 });
140 try wasm.markFunctionImport(symbol_name, i.value(wasm), i);143 try wasm.markFunctionImport(symbol_name, i.value(wasm), i);
144 log.debug("markFunctionImport intrinsic {d}={t}", .{ i, data.intrinsic });
141 },145 },
142 .call_tag_name => {146 .call_tag_name => {
143 assert(ip.indexToKey(data.ip_index) == .enum_type);147 assert(ip.indexToKey(data.ip_index) == .enum_type);
...@@ -146,11 +150,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {...@@ -146,11 +150,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
146 wasm.tag_name_table_ref_count += 1;150 wasm.tag_name_table_ref_count += 1;
147 const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu);151 const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu);
148 gop.value_ptr.* = .{ .tag_name = .{152 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}),
150 .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .slice_const_u8_sentinel_0, target),154 .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .slice_const_u8_sentinel_0, target),
151 .table_index = @intCast(wasm.tag_name_offs.items.len),155 .table_index = @intCast(wasm.tag_name_offs.items.len),
152 } };156 } };
153 try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {});
154 const tag_names = ip.loadEnumType(data.ip_index).names;157 const tag_names = ip.loadEnumType(data.ip_index).names;
155 for (tag_names.get(ip)) |tag_name| {158 for (tag_names.get(ip)) |tag_name| {
156 const slice = tag_name.toSlice(ip);159 const slice = tag_name.toSlice(ip);
...@@ -158,6 +161,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {...@@ -158,6 +161,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
158 try wasm.tag_name_bytes.appendSlice(gpa, slice[0 .. slice.len + 1]);161 try wasm.tag_name_bytes.appendSlice(gpa, slice[0 .. slice.len + 1]);
159 }162 }
160 }163 }
164 try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {});
161 },165 },
162 else => continue,166 else => continue,
163 };167 };
...@@ -1067,7 +1071,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {...@@ -1067,7 +1071,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
1067 }1071 }
10681072
1069 // Finally, write the entire binary into the file.1073 // 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, &.{});
1071 file_writer.interface.writeAll(binary_bytes.items) catch |err| switch (err) {1075 file_writer.interface.writeAll(binary_bytes.items) catch |err| switch (err) {
1072 error.WriteFailed => return file_writer.err.?,1076 error.WriteFailed => return file_writer.err.?,
1073 };1077 };
src/link/tapi.zig+7-7
...@@ -1,10 +1,10 @@...@@ -1,10 +1,10 @@
1const std = @import("std");1const std = @import("std");
2const fs = std.fs;2const Io = std.Io;
3const mem = std.mem;3const mem = std.mem;
4const log = std.log.scoped(.tapi);4const 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");
8const Yaml = yaml.Yaml;8const Yaml = yaml.Yaml;
99
10const VersionField = union(enum) {10const VersionField = union(enum) {
...@@ -130,7 +130,7 @@ pub const Tbd = union(enum) {...@@ -130,7 +130,7 @@ pub const Tbd = union(enum) {
130pub const TapiError = error{130pub const TapiError = error{
131 NotLibStub,131 NotLibStub,
132 InputOutput,132 InputOutput,
133} || yaml.YamlError || std.fs.File.PReadError;133} || yaml.YamlError || Io.File.ReadPositionalError;
134134
135pub const LibStub = struct {135pub const LibStub = struct {
136 /// Underlying memory for stub's contents.136 /// Underlying memory for stub's contents.
...@@ -139,14 +139,14 @@ pub const LibStub = struct {...@@ -139,14 +139,14 @@ pub const LibStub = struct {
139 /// Typed contents of the tbd file.139 /// Typed contents of the tbd file.
140 inner: []Tbd,140 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 {
143 const filesize = blk: {143 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);
145 break :blk @min(stat.size, std.math.maxInt(u32));145 break :blk @min(stat.size, std.math.maxInt(u32));
146 };146 };
147 const source = try allocator.alloc(u8, filesize);147 const source = try allocator.alloc(u8, filesize);
148 defer allocator.free(source);148 defer allocator.free(source);
149 const amt = try file.preadAll(source, 0);149 const amt = try file.readPositionalAll(io, source, 0);
150 if (amt != filesize) return error.InputOutput;150 if (amt != filesize) return error.InputOutput;
151151
152 var lib_stub = LibStub{152 var lib_stub = LibStub{
src/main.zig+255-244
...@@ -162,17 +162,20 @@ var debug_allocator: std.heap.DebugAllocator(.{...@@ -162,17 +162,20 @@ var debug_allocator: std.heap.DebugAllocator(.{
162 .stack_trace_frames = build_options.mem_leak_frames,162 .stack_trace_frames = build_options.mem_leak_frames,
163}) = .init;163}) = .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
165pub fn main() anyerror!void {171pub fn main() anyerror!void {
166 const gpa, const is_debug = gpa: {172 const gpa = gpa: {
167 if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true };173 if (use_debug_allocator) break :gpa debug_allocator.allocator();
168 if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false };174 if (native_os == .wasi) break :gpa std.heap.wasm_allocator;
169 if (builtin.link_libc) break :gpa .{ std.heap.c_allocator, false };175 if (builtin.link_libc) break :gpa std.heap.c_allocator;
170 break :gpa switch (builtin.mode) {176 break :gpa std.heap.smp_allocator;
171 .Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true },
172 .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false },
173 };
174 };177 };
175 defer if (is_debug) {178 defer if (use_debug_allocator) {
176 _ = debug_allocator.deinit();179 _ = debug_allocator.deinit();
177 };180 };
178 var arena_instance = std.heap.ArenaAllocator.init(gpa);181 var arena_instance = std.heap.ArenaAllocator.init(gpa);
...@@ -238,7 +241,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -238,7 +241,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
238 }241 }
239 }242 }
240243
241 var threaded: Io.Threaded = .init(gpa);244 var threaded: Io.Threaded = .init(gpa, .{});
242 defer threaded.deinit();245 defer threaded.deinit();
243 threaded_impl_ptr = &threaded;246 threaded_impl_ptr = &threaded;
244 threaded.stack_size = thread_stack_size;247 threaded.stack_size = thread_stack_size;
...@@ -328,23 +331,24 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -328,23 +331,24 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
328 .prepend_global_cache_path = true,331 .prepend_global_cache_path = true,
329 });332 });
330 } else if (mem.eql(u8, cmd, "init")) {333 } else if (mem.eql(u8, cmd, "init")) {
331 return cmdInit(gpa, arena, cmd_args);334 return cmdInit(gpa, arena, io, cmd_args);
332 } else if (mem.eql(u8, cmd, "targets")) {335 } else if (mem.eql(u8, cmd, "targets")) {
333 dev.check(.targets_command);336 dev.check(.targets_command);
334 const host = std.zig.resolveTargetQueryOrFatal(io, .{});337 const host = std.zig.resolveTargetQueryOrFatal(io, .{});
335 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);338 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
336 try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host);339 try @import("print_targets.zig").cmdTargets(arena, io, cmd_args, &stdout_writer.interface, &host);
337 return stdout_writer.interface.flush();340 return stdout_writer.interface.flush();
338 } else if (mem.eql(u8, cmd, "version")) {341 } else if (mem.eql(u8, cmd, "version")) {
339 dev.check(.version_command);342 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");
341 return;344 return;
342 } else if (mem.eql(u8, cmd, "env")) {345 } else if (mem.eql(u8, cmd, "env")) {
343 dev.check(.env_command);346 dev.check(.env_command);
344 const host = std.zig.resolveTargetQueryOrFatal(io, .{});347 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);
346 try @import("print_env.zig").cmdEnv(349 try @import("print_env.zig").cmdEnv(
347 arena,350 arena,
351 io,
348 &stdout_writer.interface,352 &stdout_writer.interface,
349 args,353 args,
350 if (native_os == .wasi) wasi_preopens,354 if (native_os == .wasi) wasi_preopens,
...@@ -358,10 +362,10 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -358,10 +362,10 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
358 });362 });
359 } else if (mem.eql(u8, cmd, "zen")) {363 } else if (mem.eql(u8, cmd, "zen")) {
360 dev.check(.zen_command);364 dev.check(.zen_command);
361 return fs.File.stdout().writeAll(info_zen);365 return Io.File.stdout().writeStreamingAll(io, info_zen);
362 } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) {366 } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) {
363 dev.check(.help_command);367 dev.check(.help_command);
364 return fs.File.stdout().writeAll(usage);368 return Io.File.stdout().writeStreamingAll(io, usage);
365 } else if (mem.eql(u8, cmd, "ast-check")) {369 } else if (mem.eql(u8, cmd, "ast-check")) {
366 return cmdAstCheck(arena, io, cmd_args);370 return cmdAstCheck(arena, io, cmd_args);
367 } else if (mem.eql(u8, cmd, "detect-cpu")) {371 } else if (mem.eql(u8, cmd, "detect-cpu")) {
...@@ -371,7 +375,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -371,7 +375,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
371 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) {375 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) {
372 return cmdDumpZir(arena, io, cmd_args);376 return cmdDumpZir(arena, io, cmd_args);
373 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "llvm-ints")) {377 } 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);
375 } else {379 } else {
376 std.log.info("{s}", .{usage});380 std.log.info("{s}", .{usage});
377 fatal("unknown command: {s}", .{args[1]});381 fatal("unknown command: {s}", .{args[1]});
...@@ -698,7 +702,7 @@ const Emit = union(enum) {...@@ -698,7 +702,7 @@ const Emit = union(enum) {
698 yes: []const u8,702 yes: []const u8,
699703
700 const OutputToCacheReason = enum { listen, @"zig run", @"zig test" };704 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 {
702 return switch (emit) {706 return switch (emit) {
703 .no => .no,707 .no => .no,
704 .yes_default_path => if (output_to_cache != null) .yes_cache else .{ .yes_path = default_basename },708 .yes_default_path => if (output_to_cache != null) .yes_cache else .{ .yes_path = default_basename },
...@@ -713,10 +717,10 @@ const Emit = union(enum) {...@@ -713,10 +717,10 @@ const Emit = union(enum) {
713 } else e: {717 } else e: {
714 // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would.718 // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would.
715 if (fs.path.dirname(path)) |dir_path| {719 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| {
717 fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) });721 fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) });
718 };722 };
719 dir.close();723 dir.close(io);
720 }724 }
721 break :e .{ .yes_path = path };725 break :e .{ .yes_path = path };
722 },726 },
...@@ -1029,13 +1033,12 @@ fn buildOutputType(...@@ -1029,13 +1033,12 @@ fn buildOutputType(
1029 if (mem.cutPrefix(u8, arg, "@")) |resp_file_path| {1033 if (mem.cutPrefix(u8, arg, "@")) |resp_file_path| {
1030 // This is a "compiler response file". We must parse the file and treat its1034 // This is a "compiler response file". We must parse the file and treat its
1031 // contents as command line parameters.1035 // contents as command line parameters.
1032 args_iter.resp_file = initArgIteratorResponseFile(arena, resp_file_path) catch |err| {1036 args_iter.resp_file = initArgIteratorResponseFile(arena, io, resp_file_path) catch |err|
1033 fatal("unable to read response file '{s}': {s}", .{ resp_file_path, @errorName(err) });1037 fatal("unable to read response file '{s}': {t}", .{ resp_file_path, err });
1034 };
1035 } else if (mem.startsWith(u8, arg, "-")) {1038 } else if (mem.startsWith(u8, arg, "-")) {
1036 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {1039 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
1037 try fs.File.stdout().writeAll(usage_build_generic);1040 try Io.File.stdout().writeStreamingAll(io, usage_build_generic);
1038 return cleanExit();1041 return cleanExit(io);
1039 } else if (mem.eql(u8, arg, "--")) {1042 } else if (mem.eql(u8, arg, "--")) {
1040 if (arg_mode == .run) {1043 if (arg_mode == .run) {
1041 // args_iter.i is 1, referring the next arg after "--" in ["--", ...]1044 // args_iter.i is 1, referring the next arg after "--" in ["--", ...]
...@@ -1856,9 +1859,7 @@ fn buildOutputType(...@@ -1856,9 +1859,7 @@ fn buildOutputType(
1856 var must_link = false;1859 var must_link = false;
1857 var file_ext: ?Compilation.FileExt = null;1860 var file_ext: ?Compilation.FileExt = null;
1858 while (it.has_next) {1861 while (it.has_next) {
1859 it.next() catch |err| {1862 it.next(io) catch |err| fatal("unable to parse command line parameters: {t}", .{err});
1860 fatal("unable to parse command line parameters: {s}", .{@errorName(err)});
1861 };
1862 switch (it.zig_equivalent) {1863 switch (it.zig_equivalent) {
1863 .target => target_arch_os_abi = it.only_arg, // example: -target riscv64-linux-unknown1864 .target => target_arch_os_abi = it.only_arg, // example: -target riscv64-linux-unknown
1864 .o => {1865 .o => {
...@@ -2834,9 +2835,9 @@ fn buildOutputType(...@@ -2834,9 +2835,9 @@ fn buildOutputType(
2834 } else if (mem.eql(u8, arg, "-V")) {2835 } else if (mem.eql(u8, arg, "-V")) {
2835 warn("ignoring request for supported emulations: unimplemented", .{});2836 warn("ignoring request for supported emulations: unimplemented", .{});
2836 } else if (mem.eql(u8, arg, "-v")) {2837 } 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");
2838 } else if (mem.eql(u8, arg, "--version")) {2839 } 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");
2840 process.exit(0);2841 process.exit(0);
2841 } else {2842 } else {
2842 fatal("unsupported linker arg: {s}", .{arg});2843 fatal("unsupported linker arg: {s}", .{arg});
...@@ -3075,14 +3076,13 @@ fn buildOutputType(...@@ -3075,14 +3076,13 @@ fn buildOutputType(
30753076
3076 const self_exe_path = switch (native_os) {3077 const self_exe_path = switch (native_os) {
3077 .wasi => {},3078 .wasi => {},
3078 else => fs.selfExePathAlloc(arena) catch |err| {3079 else => process.executablePathAlloc(io, arena) catch |err| fatal("unable to find zig self exe path: {t}", .{err}),
3079 fatal("unable to find zig self exe path: {s}", .{@errorName(err)});
3080 },
3081 };3080 };
30823081
3083 // This `init` calls `fatal` on error.3082 // This `init` calls `fatal` on error.
3084 var dirs: Compilation.Directories = .init(3083 var dirs: Compilation.Directories = .init(
3085 arena,3084 arena,
3085 io,
3086 override_lib_dir,3086 override_lib_dir,
3087 override_global_cache_dir,3087 override_global_cache_dir,
3088 s: {3088 s: {
...@@ -3095,11 +3095,9 @@ fn buildOutputType(...@@ -3095,11 +3095,9 @@ fn buildOutputType(
3095 if (native_os == .wasi) wasi_preopens,3095 if (native_os == .wasi) wasi_preopens,
3096 self_exe_path,3096 self_exe_path,
3097 );3097 );
3098 defer dirs.deinit();3098 defer dirs.deinit(io);
30993099
3100 if (linker_optimization) |o| {3100 if (linker_optimization) |o| warn("ignoring deprecated linker optimization setting '{s}'", .{o});
3101 warn("ignoring deprecated linker optimization setting '{s}'", .{o});
3102 }
31033101
3104 create_module.dirs = dirs;3102 create_module.dirs = dirs;
3105 create_module.opts.emit_llvm_ir = emit_llvm_ir != .no;3103 create_module.opts.emit_llvm_ir = emit_llvm_ir != .no;
...@@ -3208,6 +3206,7 @@ fn buildOutputType(...@@ -3208,6 +3206,7 @@ fn buildOutputType(
32083206
3209 for (create_module.framework_dirs.items) |framework_dir_path| {3207 for (create_module.framework_dirs.items) |framework_dir_path| {
3210 if (try accessFrameworkPath(3208 if (try accessFrameworkPath(
3209 io,
3211 &test_path,3210 &test_path,
3212 &checked_paths,3211 &checked_paths,
3213 framework_dir_path,3212 framework_dir_path,
...@@ -3251,8 +3250,8 @@ fn buildOutputType(...@@ -3251,8 +3250,8 @@ fn buildOutputType(
3251 }3250 }
3252 }3251 }
32533252
3254 var cleanup_emit_bin_dir: ?fs.Dir = null;3253 var cleanup_emit_bin_dir: ?Io.Dir = null;
3255 defer if (cleanup_emit_bin_dir) |*dir| dir.close();3254 defer if (cleanup_emit_bin_dir) |*dir| dir.close(io);
32563255
3257 // For `zig run` and `zig test`, we don't want to put the binary in the cwd by default. So, if3256 // For `zig run` and `zig test`, we don't want to put the binary in the cwd by default. So, if
3258 // the binary is requested with no explicit path (as is the default), we emit to the cache.3257 // the binary is requested with no explicit path (as is the default), we emit to the cache.
...@@ -3304,10 +3303,10 @@ fn buildOutputType(...@@ -3304,10 +3303,10 @@ fn buildOutputType(
3304 } else emit: {3303 } else emit: {
3305 // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would.3304 // If there's a dirname, check that dir exists. This will give a more descriptive error than `Compilation` otherwise would.
3306 if (fs.path.dirname(path)) |dir_path| {3305 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| {
3308 fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) });3307 fatal("unable to open output directory '{s}': {s}", .{ dir_path, @errorName(err) });
3309 };3308 };
3310 dir.close();3309 dir.close(io);
3311 }3310 }
3312 break :emit .{ .yes_path = path };3311 break :emit .{ .yes_path = path };
3313 },3312 },
...@@ -3321,18 +3320,18 @@ fn buildOutputType(...@@ -3321,18 +3320,18 @@ fn buildOutputType(
3321 };3320 };
33223321
3323 const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name});3322 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
3326 const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name});3325 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
3329 const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name});3328 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
3332 const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name});3331 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
3337 const is_exe_or_dyn_lib = switch (create_module.resolved_options.output_mode) {3336 const is_exe_or_dyn_lib = switch (create_module.resolved_options.output_mode) {
3338 .Obj => false,3337 .Obj => false,
...@@ -3353,7 +3352,7 @@ fn buildOutputType(...@@ -3353,7 +3352,7 @@ fn buildOutputType(
3353 const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name});3352 const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name});
3354 const emit_implib_resolved: Compilation.CreateOptions.Emit = switch (emit_implib) {3353 const emit_implib_resolved: Compilation.CreateOptions.Emit = switch (emit_implib) {
3355 .no => .no,3354 .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),
3357 .yes_default_path => emit: {3356 .yes_default_path => emit: {
3358 if (output_to_cache != null) break :emit .yes_cache;3357 if (output_to_cache != null) break :emit .yes_cache;
3359 const p = try fs.path.join(arena, &.{3358 const p = try fs.path.join(arena, &.{
...@@ -3382,24 +3381,24 @@ fn buildOutputType(...@@ -3382,24 +3381,24 @@ fn buildOutputType(
3382 const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{3381 const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{
3383 std.crypto.random.int(u64), ext.canonicalName(target),3382 std.crypto.random.int(u64), ext.canonicalName(target),
3384 });3383 });
3385 try dirs.local_cache.handle.makePath("tmp");3384 try dirs.local_cache.handle.createDirPath(io, "tmp");
33863385
3387 // Note that in one of the happy paths, execve() is used to switch to3386 // Note that in one of the happy paths, execve() is used to switch to
3388 // clang in which case any cleanup logic that exists for this temporary3387 // clang in which case any cleanup logic that exists for this temporary
3389 // file will not run and this temp file will be leaked. The filename3388 // file will not run and this temp file will be leaked. The filename
3390 // will be a hash of its contents — so multiple invocations of3389 // will be a hash of its contents — so multiple invocations of
3391 // `zig cc -` will result in the same temp file name.3390 // `zig cc -` will result in the same temp file name.
3392 var f = try dirs.local_cache.handle.createFile(dump_path, .{});3391 var f = try dirs.local_cache.handle.createFile(io, dump_path, .{});
3393 defer f.close();3392 defer f.close(io);
33943393
3395 // Re-using the hasher from Cache, since the functional requirements3394 // Re-using the hasher from Cache, since the functional requirements
3396 // for the hashing algorithm here and in the cache are the same.3395 // for the hashing algorithm here and in the cache are the same.
3397 // We are providing our own cache key, because this file has nothing3396 // We are providing our own cache key, because this file has nothing
3398 // to do with the cache manifest.3397 // to do with the cache manifest.
3399 var file_writer = f.writer(&.{});3398 var file_writer = f.writer(io, &.{});
3400 var buffer: [1000]u8 = undefined;3399 var buffer: [1000]u8 = undefined;
3401 var hasher = file_writer.interface.hashed(Cache.Hasher.init("0123456789abcdef"), &buffer);3400 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, &.{});
3403 _ = hasher.writer.sendFileAll(&stdin_reader, .unlimited) catch |err| switch (err) {3402 _ = hasher.writer.sendFileAll(&stdin_reader, .unlimited) catch |err| switch (err) {
3404 error.WriteFailed => fatal("failed to write {s}: {t}", .{ dump_path, file_writer.err.? }),3403 error.WriteFailed => fatal("failed to write {s}: {t}", .{ dump_path, file_writer.err.? }),
3405 else => fatal("failed to pipe stdin to {s}: {t}", .{ dump_path, err }),3404 else => fatal("failed to pipe stdin to {s}: {t}", .{ dump_path, err }),
...@@ -3411,7 +3410,7 @@ fn buildOutputType(...@@ -3411,7 +3410,7 @@ fn buildOutputType(
3411 const sub_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-stdin{s}", .{3410 const sub_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-stdin{s}", .{
3412 &bin_digest, ext.canonicalName(target),3411 &bin_digest, ext.canonicalName(target),
3413 });3412 });
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
3416 // Convert `sub_path` to be relative to current working directory.3415 // Convert `sub_path` to be relative to current working directory.
3417 src.src_path = try dirs.local_cache.join(arena, &.{sub_path});3416 src.src_path = try dirs.local_cache.join(arena, &.{sub_path});
...@@ -3630,13 +3629,13 @@ fn buildOutputType(...@@ -3630,13 +3629,13 @@ fn buildOutputType(
3630 if (show_builtin) {3629 if (show_builtin) {
3631 const builtin_opts = comp.root_mod.getBuiltinOptions(comp.config);3630 const builtin_opts = comp.root_mod.getBuiltinOptions(comp.config);
3632 const source = try builtin_opts.generate(arena);3631 const source = try builtin_opts.generate(arena);
3633 return fs.File.stdout().writeAll(source);3632 return Io.File.stdout().writeStreamingAll(io, source);
3634 }3633 }
3635 switch (listen) {3634 switch (listen) {
3636 .none => {},3635 .none => {},
3637 .stdio => {3636 .stdio => {
3638 var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer);3637 var stdin_reader = Io.File.stdin().reader(io, &stdin_buffer);
3639 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);3638 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
3640 try serve(3639 try serve(
3641 comp,3640 comp,
3642 &stdin_reader.interface,3641 &stdin_reader.interface,
...@@ -3647,7 +3646,7 @@ fn buildOutputType(...@@ -3647,7 +3646,7 @@ fn buildOutputType(
3647 all_args,3646 all_args,
3648 runtime_args_start,3647 runtime_args_start,
3649 );3648 );
3650 return cleanExit();3649 return cleanExit(io);
3651 },3650 },
3652 .ip4 => |ip4_addr| {3651 .ip4 => |ip4_addr| {
3653 const addr: Io.net.IpAddress = .{ .ip4 = ip4_addr };3652 const addr: Io.net.IpAddress = .{ .ip4 = ip4_addr };
...@@ -3673,12 +3672,12 @@ fn buildOutputType(...@@ -3673,12 +3672,12 @@ fn buildOutputType(
3673 all_args,3672 all_args,
3674 runtime_args_start,3673 runtime_args_start,
3675 );3674 );
3676 return cleanExit();3675 return cleanExit(io);
3677 },3676 },
3678 }3677 }
36793678
3680 {3679 {
3681 const root_prog_node = std.Progress.start(.{3680 const root_prog_node = std.Progress.start(io, .{
3682 .disable_printing = (color == .off),3681 .disable_printing = (color == .off),
3683 });3682 });
3684 defer root_prog_node.end();3683 defer root_prog_node.end();
...@@ -3756,7 +3755,7 @@ fn buildOutputType(...@@ -3756,7 +3755,7 @@ fn buildOutputType(
3756 }3755 }
37573756
3758 // Skip resource deallocation in release builds; let the OS do it.3757 // Skip resource deallocation in release builds; let the OS do it.
3759 return cleanExit();3758 return cleanExit(io);
3760}3759}
37613760
3762const CreateModule = struct {3761const CreateModule = struct {
...@@ -3927,11 +3926,8 @@ fn createModule(...@@ -3927,11 +3926,8 @@ fn createModule(
3927 }3926 }
39283927
3929 if (target.isMinGW()) {3928 if (target.isMinGW()) {
3930 const exists = mingw.libExists(arena, target, create_module.dirs.zig_lib, lib_name) catch |err| {3929 const exists = mingw.libExists(arena, io, target, create_module.dirs.zig_lib, lib_name) catch |err|
3931 fatal("failed to check zig installation for DLL import libs: {s}", .{3930 fatal("failed to check zig installation for DLL import libs: {t}", .{err});
3932 @errorName(err),
3933 });
3934 };
3935 if (exists) {3931 if (exists) {
3936 try create_module.windows_libs.put(arena, lib_name, {});3932 try create_module.windows_libs.put(arena, lib_name, {});
3937 continue;3933 continue;
...@@ -3959,14 +3955,14 @@ fn createModule(...@@ -3959,14 +3955,14 @@ fn createModule(
3959 if (fs.path.isAbsolute(lib_dir_arg)) {3955 if (fs.path.isAbsolute(lib_dir_arg)) {
3960 const stripped_dir = lib_dir_arg[fs.path.parsePath(lib_dir_arg).root.len..];3956 const stripped_dir = lib_dir_arg[fs.path.parsePath(lib_dir_arg).root.len..];
3961 const full_path = try fs.path.join(arena, &[_][]const u8{ root, stripped_dir });3957 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);
3963 } else {3959 } else {
3964 addLibDirectoryWarn(&create_module.lib_directories, lib_dir_arg);3960 addLibDirectoryWarn(io, &create_module.lib_directories, lib_dir_arg);
3965 }3961 }
3966 }3962 }
3967 } else {3963 } else {
3968 for (create_module.lib_dir_args.items) |lib_dir_arg| {3964 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);
3970 }3966 }
3971 }3967 }
3972 create_module.lib_dir_args = undefined; // From here we use lib_directories instead.3968 create_module.lib_dir_args = undefined; // From here we use lib_directories instead.
...@@ -3989,9 +3985,8 @@ fn createModule(...@@ -3989,9 +3985,8 @@ fn createModule(
3989 resolved_target.is_native_os and resolved_target.is_native_abi and3985 resolved_target.is_native_os and resolved_target.is_native_abi and
3990 create_module.want_native_include_dirs)3986 create_module.want_native_include_dirs)
3991 {3987 {
3992 var paths = std.zig.system.NativePaths.detect(arena, target) catch |err| {3988 var paths = std.zig.system.NativePaths.detect(arena, io, target) catch |err|
3993 fatal("unable to detect native system paths: {s}", .{@errorName(err)});3989 fatal("unable to detect native system paths: {t}", .{err});
3994 };
3995 for (paths.warnings.items) |warning| {3990 for (paths.warnings.items) |warning| {
3996 warn("{s}", .{warning});3991 warn("{s}", .{warning});
3997 }3992 }
...@@ -4002,38 +3997,35 @@ fn createModule(...@@ -4002,38 +3997,35 @@ fn createModule(
4002 try create_module.rpath_list.appendSlice(arena, paths.rpaths.items);3997 try create_module.rpath_list.appendSlice(arena, paths.rpaths.items);
40033998
4004 try create_module.lib_directories.ensureUnusedCapacity(arena, paths.lib_dirs.items.len);3999 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);
4006 }4001 }
40074002
4008 if (create_module.libc_paths_file) |paths_file| {4003 if (create_module.libc_paths_file) |paths_file| {
4009 create_module.libc_installation = LibCInstallation.parse(arena, paths_file, target) catch |err| {4004 create_module.libc_installation = LibCInstallation.parse(arena, io, paths_file, target) catch |err|
4010 fatal("unable to parse libc paths file at path {s}: {s}", .{4005 fatal("unable to parse libc paths file at path {s}: {t}", .{ paths_file, err });
4011 paths_file, @errorName(err),
4012 });
4013 };
4014 }4006 }
40154007
4016 if (target.os.tag == .windows and (target.abi == .msvc or target.abi == .itanium) and4008 if (target.os.tag == .windows and (target.abi == .msvc or target.abi == .itanium) and
4017 any_name_queries_remaining)4009 any_name_queries_remaining)
4018 {4010 {
4019 if (create_module.libc_installation == null) {4011 if (create_module.libc_installation == null) {
4020 create_module.libc_installation = LibCInstallation.findNative(.{4012 create_module.libc_installation = LibCInstallation.findNative(arena, io, .{
4021 .allocator = arena,
4022 .verbose = true,4013 .verbose = true,
4023 .target = target,4014 .target = target,
4024 }) catch |err| {4015 }) catch |err| {
4025 fatal("unable to find native libc installation: {s}", .{@errorName(err)});4016 fatal("unable to find native libc installation: {t}", .{err});
4026 };4017 };
4027 }4018 }
4028 try create_module.lib_directories.ensureUnusedCapacity(arena, 2);4019 try create_module.lib_directories.ensureUnusedCapacity(arena, 2);
4029 addLibDirectoryWarn(&create_module.lib_directories, create_module.libc_installation.?.msvc_lib_dir.?);4020 addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.msvc_lib_dir.?);
4030 addLibDirectoryWarn(&create_module.lib_directories, create_module.libc_installation.?.kernel32_lib_dir.?);4021 addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.kernel32_lib_dir.?);
4031 }4022 }
40324023
4033 // Destructively mutates but does not transfer ownership of `unresolved_link_inputs`.4024 // Destructively mutates but does not transfer ownership of `unresolved_link_inputs`.
4034 link.resolveInputs(4025 link.resolveInputs(
4035 gpa,4026 gpa,
4036 arena,4027 arena,
4028 io,
4037 target,4029 target,
4038 &unresolved_link_inputs,4030 &unresolved_link_inputs,
4039 &create_module.link_inputs,4031 &create_module.link_inputs,
...@@ -4160,7 +4152,9 @@ fn serve(...@@ -4160,7 +4152,9 @@ fn serve(
41604152
4161 var child_pid: ?std.process.Child.Id = null;4153 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
4164 const file_system_inputs = comp.file_system_inputs.?;4158 const file_system_inputs = comp.file_system_inputs.?;
41654159
4166 const IncrementalDebugServer = if (build_options.enable_debug_extensions and !builtin.single_threaded)4160 const IncrementalDebugServer = if (build_options.enable_debug_extensions and !builtin.single_threaded)
...@@ -4183,7 +4177,7 @@ fn serve(...@@ -4183,7 +4177,7 @@ fn serve(
4183 defer if (comp.debugIncremental()) ids.mutex.unlock(io);4177 defer if (comp.debugIncremental()) ids.mutex.unlock(io);
41844178
4185 switch (hdr.tag) {4179 switch (hdr.tag) {
4186 .exit => return cleanExit(),4180 .exit => return cleanExit(io),
4187 .update => {4181 .update => {
4188 tracy.frameMark();4182 tracy.frameMark();
4189 file_system_inputs.clearRetainingCapacity();4183 file_system_inputs.clearRetainingCapacity();
...@@ -4436,12 +4430,12 @@ fn runOrTest(...@@ -4436,12 +4430,12 @@ fn runOrTest(
4436 // the error message and invocation below.4430 // the error message and invocation below.
4437 if (process.can_execv and arg_mode == .run) {4431 if (process.can_execv and arg_mode == .run) {
4438 // execv releases the locks; no need to destroy the Compilation here.4432 // execv releases the locks; no need to destroy the Compilation here.
4439 std.debug.lockStdErr();4433 _ = try io.lockStderr(&.{}, .no_color);
4440 const err = process.execve(gpa, argv.items, &env_map);4434 const err = process.execve(gpa, argv.items, &env_map);
4441 std.debug.unlockStdErr();4435 io.unlockStderr();
4442 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);4436 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
4443 const cmd = try std.mem.join(arena, " ", argv.items);4437 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 });
4445 } else if (process.can_spawn) {4439 } else if (process.can_spawn) {
4446 var child = std.process.Child.init(argv.items, gpa);4440 var child = std.process.Child.init(argv.items, gpa);
4447 child.env_map = &env_map;4441 child.env_map = &env_map;
...@@ -4455,9 +4449,9 @@ fn runOrTest(...@@ -4455,9 +4449,9 @@ fn runOrTest(
4455 comp_destroyed.* = true;4449 comp_destroyed.* = true;
44564450
4457 const term_result = t: {4451 const term_result = t: {
4458 std.debug.lockStdErr();4452 _ = try io.lockStderr(&.{}, .no_color);
4459 defer std.debug.unlockStdErr();4453 defer io.unlockStderr();
4460 break :t child.spawnAndWait();4454 break :t child.spawnAndWait(io);
4461 };4455 };
4462 const term = term_result catch |err| {4456 const term = term_result catch |err| {
4463 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);4457 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
...@@ -4469,7 +4463,7 @@ fn runOrTest(...@@ -4469,7 +4463,7 @@ fn runOrTest(
4469 switch (term) {4463 switch (term) {
4470 .Exited => |code| {4464 .Exited => |code| {
4471 if (code == 0) {4465 if (code == 0) {
4472 return cleanExit();4466 return cleanExit(io);
4473 } else {4467 } else {
4474 process.exit(code);4468 process.exit(code);
4475 }4469 }
...@@ -4483,7 +4477,7 @@ fn runOrTest(...@@ -4483,7 +4477,7 @@ fn runOrTest(
4483 switch (term) {4477 switch (term) {
4484 .Exited => |code| {4478 .Exited => |code| {
4485 if (code == 0) {4479 if (code == 0) {
4486 return cleanExit();4480 return cleanExit(io);
4487 } else {4481 } else {
4488 const cmd = try std.mem.join(arena, " ", argv.items);4482 const cmd = try std.mem.join(arena, " ", argv.items);
4489 fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd });4483 fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd });
...@@ -4512,6 +4506,7 @@ fn runOrTestHotSwap(...@@ -4512,6 +4506,7 @@ fn runOrTestHotSwap(
4512 all_args: []const []const u8,4506 all_args: []const []const u8,
4513 runtime_args_start: ?usize,4507 runtime_args_start: ?usize,
4514) !std.process.Child.Id {4508) !std.process.Child.Id {
4509 const io = comp.io;
4515 const lf = comp.bin_file.?;4510 const lf = comp.bin_file.?;
45164511
4517 const exe_path = switch (builtin.target.os.tag) {4512 const exe_path = switch (builtin.target.os.tag) {
...@@ -4520,7 +4515,7 @@ fn runOrTestHotSwap(...@@ -4520,7 +4515,7 @@ fn runOrTestHotSwap(
4520 // tmp zig-cache and use it to spawn the child process. This way we are free to update4515 // tmp zig-cache and use it to spawn the child process. This way we are free to update
4521 // the binary with each requested hot update.4516 // the binary with each requested hot update.
4522 .windows => blk: {4517 .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, .{});
4524 break :blk try fs.path.join(gpa, &.{ comp.dirs.local_cache.path orelse ".", lf.emit.sub_path });4519 break :blk try fs.path.join(gpa, &.{ comp.dirs.local_cache.path orelse ".", lf.emit.sub_path });
4525 },4520 },
45264521
...@@ -4593,7 +4588,7 @@ fn runOrTestHotSwap(...@@ -4593,7 +4588,7 @@ fn runOrTestHotSwap(
4593 child.stdout_behavior = .Inherit;4588 child.stdout_behavior = .Inherit;
4594 child.stderr_behavior = .Inherit;4589 child.stderr_behavior = .Inherit;
45954590
4596 try child.spawn();4591 try child.spawn(io);
45974592
4598 return child.id;4593 return child.id;
4599 },4594 },
...@@ -4604,6 +4599,8 @@ const UpdateModuleError = Compilation.UpdateError || error{...@@ -4604,6 +4599,8 @@ const UpdateModuleError = Compilation.UpdateError || error{
4604 /// The update caused compile errors. The error bundle has already been4599 /// The update caused compile errors. The error bundle has already been
4605 /// reported to the user by being rendered to stderr.4600 /// reported to the user by being rendered to stderr.
4606 CompileErrorsReported,4601 CompileErrorsReported,
4602 /// Error occurred printing compilation errors to stderr.
4603 PrintingErrorsFailed,
4607};4604};
4608fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node) UpdateModuleError!void {4605fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node) UpdateModuleError!void {
4609 try comp.update(prog_node);4606 try comp.update(prog_node);
...@@ -4612,7 +4609,11 @@ fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node)...@@ -4612,7 +4609,11 @@ fn updateModule(comp: *Compilation, color: Color, prog_node: std.Progress.Node)
4612 defer errors.deinit(comp.gpa);4609 defer errors.deinit(comp.gpa);
46134610
4614 if (errors.errorMessageCount() > 0) {4611 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 };
4616 return error.CompileErrorsReported;4617 return error.CompileErrorsReported;
4617 }4618 }
4618}4619}
...@@ -4665,7 +4666,7 @@ fn cmdTranslateC(...@@ -4665,7 +4666,7 @@ fn cmdTranslateC(
4665 return;4666 return;
4666 } else {4667 } else {
4667 const color: Color = .auto;4668 const color: Color = .auto;
4668 result.errors.renderToStdErr(.{}, color);4669 result.errors.renderToStderr(io, .{}, color) catch {};
4669 process.exit(1);4670 process.exit(1);
4670 }4671 }
4671 }4672 }
...@@ -4680,7 +4681,7 @@ fn cmdTranslateC(...@@ -4680,7 +4681,7 @@ fn cmdTranslateC(
4680 } else {4681 } else {
4681 const hex_digest = Cache.binToHex(result.digest);4682 const hex_digest = Cache.binToHex(result.digest);
4682 const out_zig_path = try fs.path.join(arena, &.{ "o", &hex_digest, translated_basename });4683 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| {
4684 const path = comp.dirs.local_cache.path orelse ".";4685 const path = comp.dirs.local_cache.path orelse ".";
4685 fatal("unable to open cached translated zig file '{s}{s}{s}': {s}", .{4686 fatal("unable to open cached translated zig file '{s}{s}{s}': {s}", .{
4686 path,4687 path,
...@@ -4689,12 +4690,12 @@ fn cmdTranslateC(...@@ -4689,12 +4690,12 @@ fn cmdTranslateC(
4689 @errorName(err),4690 @errorName(err),
4690 });4691 });
4691 };4692 };
4692 defer zig_file.close();4693 defer zig_file.close(io);
4693 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);4694 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
4694 var file_reader = zig_file.reader(io, &.{});4695 var file_reader = zig_file.reader(io, &.{});
4695 _ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited);4696 _ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited);
4696 try stdout_writer.interface.flush();4697 try stdout_writer.interface.flush();
4697 return cleanExit();4698 return cleanExit(io);
4698 }4699 }
4699}4700}
47004701
...@@ -4728,7 +4729,7 @@ const usage_init =...@@ -4728,7 +4729,7 @@ const usage_init =
4728 \\4729 \\
4729;4730;
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 {
4732 dev.check(.init_command);4733 dev.check(.init_command);
47334734
4734 var template: enum { example, minimal } = .example;4735 var template: enum { example, minimal } = .example;
...@@ -4740,8 +4741,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4740,8 +4741,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4740 if (mem.eql(u8, arg, "-m") or mem.eql(u8, arg, "--minimal")) {4741 if (mem.eql(u8, arg, "-m") or mem.eql(u8, arg, "--minimal")) {
4741 template = .minimal;4742 template = .minimal;
4742 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {4743 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
4743 try fs.File.stdout().writeAll(usage_init);4744 try Io.File.stdout().writeStreamingAll(io, usage_init);
4744 return cleanExit();4745 return cleanExit(io);
4745 } else {4746 } else {
4746 fatal("unrecognized parameter: '{s}'", .{arg});4747 fatal("unrecognized parameter: '{s}'", .{arg});
4747 }4748 }
...@@ -4759,8 +4760,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4759,8 +4760,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
47594760
4760 switch (template) {4761 switch (template) {
4761 .example => {4762 .example => {
4762 var templates = findTemplates(gpa, arena);4763 var templates = findTemplates(gpa, arena, io);
4763 defer templates.deinit();4764 defer templates.deinit(io);
47644765
4765 const s = fs.path.sep_str;4766 const s = fs.path.sep_str;
4766 const template_paths = [_][]const u8{4767 const template_paths = [_][]const u8{
...@@ -4772,7 +4773,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4772,7 +4773,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4772 var ok_count: usize = 0;4773 var ok_count: usize = 0;
47734774
4774 for (template_paths) |template_path| {4775 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)) |_| {
4776 std.log.info("created {s}", .{template_path});4777 std.log.info("created {s}", .{template_path});
4777 ok_count += 1;4778 ok_count += 1;
4778 } else |err| switch (err) {4779 } else |err| switch (err) {
...@@ -4786,10 +4787,10 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4786,10 +4787,10 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4786 if (ok_count == template_paths.len) {4787 if (ok_count == template_paths.len) {
4787 std.log.info("see `zig build --help` for a menu of options", .{});4788 std.log.info("see `zig build --help` for a menu of options", .{});
4788 }4789 }
4789 return cleanExit();4790 return cleanExit(io);
4790 },4791 },
4791 .minimal => {4792 .minimal => {
4792 writeSimpleTemplateFile(Package.Manifest.basename,4793 writeSimpleTemplateFile(io, Package.Manifest.basename,
4793 \\.{{4794 \\.{{
4794 \\ .name = .{s},4795 \\ .name = .{s},
4795 \\ .version = "0.0.1",4796 \\ .version = "0.0.1",
...@@ -4806,7 +4807,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4806,7 +4807,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4806 else => fatal("failed to create '{s}': {s}", .{ Package.Manifest.basename, @errorName(err) }),4807 else => fatal("failed to create '{s}': {s}", .{ Package.Manifest.basename, @errorName(err) }),
4807 error.PathAlreadyExists => fatal("refusing to overwrite '{s}'", .{Package.Manifest.basename}),4808 error.PathAlreadyExists => fatal("refusing to overwrite '{s}'", .{Package.Manifest.basename}),
4808 };4809 };
4809 writeSimpleTemplateFile(Package.build_zig_basename,4810 writeSimpleTemplateFile(io, Package.build_zig_basename,
4810 \\const std = @import("std");4811 \\const std = @import("std");
4811 \\4812 \\
4812 \\pub fn build(b: *std.Build) void {{4813 \\pub fn build(b: *std.Build) void {{
...@@ -4819,11 +4820,11 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4819,11 +4820,11 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4819 // their `build.zig.zon` *after* writing their `build.zig`. So this one isn't fatal.4820 // their `build.zig.zon` *after* writing their `build.zig`. So this one isn't fatal.
4820 error.PathAlreadyExists => {4821 error.PathAlreadyExists => {
4821 std.log.info("successfully populated '{s}', preserving existing '{s}'", .{ Package.Manifest.basename, Package.build_zig_basename });4822 std.log.info("successfully populated '{s}', preserving existing '{s}'", .{ Package.Manifest.basename, Package.build_zig_basename });
4822 return cleanExit();4823 return cleanExit(io);
4823 },4824 },
4824 };4825 };
4825 std.log.info("successfully populated '{s}' and '{s}'", .{ Package.Manifest.basename, Package.build_zig_basename });4826 std.log.info("successfully populated '{s}' and '{s}'", .{ Package.Manifest.basename, Package.build_zig_basename });
4826 return cleanExit();4827 return cleanExit(io);
4827 },4828 },
4828 }4829 }
4829}4830}
...@@ -4894,7 +4895,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -4894,7 +4895,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
4894 const argv_index_exe = child_argv.items.len;4895 const argv_index_exe = child_argv.items.len;
4895 _ = try child_argv.addOne();4896 _ = try child_argv.addOne();
48964897
4897 const self_exe_path = try fs.selfExePathAlloc(arena);4898 const self_exe_path = try process.executablePathAlloc(io, arena);
4898 try child_argv.append(self_exe_path);4899 try child_argv.append(self_exe_path);
48994900
4900 const argv_index_zig_lib_dir = child_argv.items.len;4901 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)...@@ -5075,7 +5076,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
50755076
5076 const work_around_btrfs_bug = native_os == .linux and5077 const work_around_btrfs_bug = native_os == .linux and
5077 EnvVar.ZIG_BTRFS_WORKAROUND.isSet();5078 EnvVar.ZIG_BTRFS_WORKAROUND.isSet();
5078 const root_prog_node = std.Progress.start(.{5079 const root_prog_node = std.Progress.start(io, .{
5079 .disable_printing = (color == .off),5080 .disable_printing = (color == .off),
5080 .root_name = "Compile Build Script",5081 .root_name = "Compile Build Script",
5081 });5082 });
...@@ -5110,14 +5111,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5110,14 +5111,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5110 const paths_file = debug_libc_paths_file orelse break :lci null;5111 const paths_file = debug_libc_paths_file orelse break :lci null;
5111 if (!build_options.enable_debug_extensions) unreachable;5112 if (!build_options.enable_debug_extensions) unreachable;
5112 const lci = try arena.create(LibCInstallation);5113 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);
5114 break :lci lci;5115 break :lci lci;
5115 };5116 };
51165117
5117 process.raiseFileDescriptorLimit();5118 process.raiseFileDescriptorLimit();
51185119
5119 const cwd_path = try introspect.getResolvedCwd(arena);5120 const cwd_path = try introspect.getResolvedCwd(arena);
5120 const build_root = try findBuildRoot(arena, .{5121 const build_root = try findBuildRoot(arena, io, .{
5121 .cwd_path = cwd_path,5122 .cwd_path = cwd_path,
5122 .build_file = build_file,5123 .build_file = build_file,
5123 });5124 });
...@@ -5125,6 +5126,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5125,6 +5126,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5125 // This `init` calls `fatal` on error.5126 // This `init` calls `fatal` on error.
5126 var dirs: Compilation.Directories = .init(5127 var dirs: Compilation.Directories = .init(
5127 arena,5128 arena,
5129 io,
5128 override_lib_dir,5130 override_lib_dir,
5129 override_global_cache_dir,5131 override_global_cache_dir,
5130 .{ .override = path: {5132 .{ .override = path: {
...@@ -5134,7 +5136,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5134,7 +5136,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5134 {},5136 {},
5135 self_exe_path,5137 self_exe_path,
5136 );5138 );
5137 defer dirs.deinit();5139 defer dirs.deinit(io);
51385140
5139 child_argv.items[argv_index_zig_lib_dir] = dirs.zig_lib.path orelse cwd_path;5141 child_argv.items[argv_index_zig_lib_dir] = dirs.zig_lib.path orelse cwd_path;
5140 child_argv.items[argv_index_build_file] = build_root.directory.path orelse cwd_path;5142 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)...@@ -5203,8 +5205,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5203 .parent = root_mod,5205 .parent = root_mod,
5204 });5206 });
52055207
5206 var cleanup_build_dir: ?fs.Dir = null;5208 var cleanup_build_dir: ?Io.Dir = null;
5207 defer if (cleanup_build_dir) |*dir| dir.close();5209 defer if (cleanup_build_dir) |*dir| dir.close(io);
52085210
5209 if (dev.env.supports(.fetch_command)) {5211 if (dev.env.supports(.fetch_command)) {
5210 const fetch_prog_node = root_prog_node.start("Fetch Packages", 0);5212 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)...@@ -5226,7 +5228,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5226 if (system_pkg_dir_path) |p| {5228 if (system_pkg_dir_path) |p| {
5227 job_queue.global_cache = .{5229 job_queue.global_cache = .{
5228 .path = p,5230 .path = p,
5229 .handle = fs.cwd().openDir(p, .{}) catch |err| {5231 .handle = Io.Dir.cwd().openDir(io, p, .{}) catch |err| {
5230 fatal("unable to open system package directory '{s}': {s}", .{5232 fatal("unable to open system package directory '{s}': {s}", .{
5231 p, @errorName(err),5233 p, @errorName(err),
5232 });5234 });
...@@ -5285,17 +5287,18 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5285,17 +5287,18 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
52855287
5286 if (fetch.error_bundle.root_list.items.len > 0) {5288 if (fetch.error_bundle.root_list.items.len > 0) {
5287 var errors = try fetch.error_bundle.toOwnedBundle("");5289 var errors = try fetch.error_bundle.toOwnedBundle("");
5288 errors.renderToStdErr(.{}, color);5290 errors.renderToStderr(io, .{}, color) catch {};
5289 process.exit(1);5291 process.exit(1);
5290 }5292 }
52915293
5292 if (fetch_only) return cleanExit();5294 if (fetch_only) return cleanExit(io);
52935295
5294 var source_buf = std.array_list.Managed(u8).init(gpa);5296 var source_buf = std.array_list.Managed(u8).init(gpa);
5295 defer source_buf.deinit();5297 defer source_buf.deinit();
5296 try job_queue.createDependenciesSource(&source_buf);5298 try job_queue.createDependenciesSource(&source_buf);
5297 const deps_mod = try createDependenciesModule(5299 const deps_mod = try createDependenciesModule(
5298 arena,5300 arena,
5301 io,
5299 source_buf.items,5302 source_buf.items,
5300 root_mod,5303 root_mod,
5301 dirs,5304 dirs,
...@@ -5357,6 +5360,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5357,6 +5360,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5357 }5360 }
5358 } else try createEmptyDependenciesModule(5361 } else try createEmptyDependenciesModule(
5359 arena,5362 arena,
5363 io,
5360 root_mod,5364 root_mod,
5361 dirs,5365 dirs,
5362 config,5366 config,
...@@ -5415,16 +5419,15 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5415,16 +5419,15 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5415 child.stderr_behavior = .Inherit;5419 child.stderr_behavior = .Inherit;
54165420
5417 const term = t: {5421 const term = t: {
5418 std.debug.lockStdErr();5422 _ = try io.lockStderr(&.{}, .no_color);
5419 defer std.debug.unlockStdErr();5423 defer io.unlockStderr();
5420 break :t child.spawnAndWait() catch |err| {5424 break :t child.spawnAndWait(io) catch |err|
5421 fatal("failed to spawn build runner {s}: {s}", .{ child_argv.items[0], @errorName(err) });5425 fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err });
5422 };
5423 };5426 };
54245427
5425 switch (term) {5428 switch (term) {
5426 .Exited => |code| {5429 .Exited => |code| {
5427 if (code == 0) return cleanExit();5430 if (code == 0) return cleanExit(io);
5428 // Indicates that the build runner has reported compile errors5431 // Indicates that the build runner has reported compile errors
5429 // and this parent process does not need to report any further5432 // and this parent process does not need to report any further
5430 // diagnostics.5433 // diagnostics.
...@@ -5437,12 +5440,12 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5437,12 +5440,12 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5437 // that are missing.5440 // that are missing.
5438 const s = fs.path.sep_str;5441 const s = fs.path.sep_str;
5439 const tmp_sub_path = "tmp" ++ s ++ results_tmp_file_nonce;5442 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| {
5441 fatal("unable to read results of configure phase from '{f}{s}': {s}", .{5444 fatal("unable to read results of configure phase from '{f}{s}': {s}", .{
5442 dirs.local_cache, tmp_sub_path, @errorName(err),5445 dirs.local_cache, tmp_sub_path, @errorName(err),
5443 });5446 });
5444 };5447 };
5445 dirs.local_cache.handle.deleteFile(tmp_sub_path) catch {};5448 dirs.local_cache.handle.deleteFile(io, tmp_sub_path) catch {};
54465449
5447 var it = mem.splitScalar(u8, stdout, '\n');5450 var it = mem.splitScalar(u8, stdout, '\n');
5448 var any_errors = false;5451 var any_errors = false;
...@@ -5511,9 +5514,10 @@ fn jitCmd(...@@ -5511,9 +5514,10 @@ fn jitCmd(
5511 dev.check(.jit_command);5514 dev.check(.jit_command);
55125515
5513 const color: Color = .auto;5516 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, .{
5515 .disable_printing = (color == .off),5518 .disable_printing = (color == .off),
5516 });5519 });
5520 defer root_prog_node.end();
55175521
5518 const target_query: std.Target.Query = .{};5522 const target_query: std.Target.Query = .{};
5519 const resolved_target: Package.Module.ResolvedTarget = .{5523 const resolved_target: Package.Module.ResolvedTarget = .{
...@@ -5523,9 +5527,8 @@ fn jitCmd(...@@ -5523,9 +5527,8 @@ fn jitCmd(
5523 .is_explicit_dynamic_linker = false,5527 .is_explicit_dynamic_linker = false,
5524 };5528 };
55255529
5526 const self_exe_path = fs.selfExePathAlloc(arena) catch |err| {5530 const self_exe_path = process.executablePathAlloc(io, arena) catch |err|
5527 fatal("unable to find self exe path: {s}", .{@errorName(err)});5531 fatal("unable to find self exe path: {t}", .{err});
5528 };
55295532
5530 const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet())5533 const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet())
5531 .Debug5534 .Debug
...@@ -5538,13 +5541,14 @@ fn jitCmd(...@@ -5538,13 +5541,14 @@ fn jitCmd(
5538 // This `init` calls `fatal` on error.5541 // This `init` calls `fatal` on error.
5539 var dirs: Compilation.Directories = .init(5542 var dirs: Compilation.Directories = .init(
5540 arena,5543 arena,
5544 io,
5541 override_lib_dir,5545 override_lib_dir,
5542 override_global_cache_dir,5546 override_global_cache_dir,
5543 .global,5547 .global,
5544 if (native_os == .wasi) wasi_preopens,5548 if (native_os == .wasi) wasi_preopens,
5545 self_exe_path,5549 self_exe_path,
5546 );5550 );
5547 defer dirs.deinit();5551 defer dirs.deinit(io);
55485552
5549 const thread_limit = @min(5553 const thread_limit = @min(
5550 @max(std.Thread.getCpuCount() catch 1, 1),5554 @max(std.Thread.getCpuCount() catch 1, 1),
...@@ -5623,7 +5627,7 @@ fn jitCmd(...@@ -5623,7 +5627,7 @@ fn jitCmd(
5623 defer comp.destroy();5627 defer comp.destroy();
56245628
5625 if (options.server) {5629 if (options.server) {
5626 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);5630 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
5627 var server: std.zig.Server = .{5631 var server: std.zig.Server = .{
5628 .out = &stdout_writer.interface,5632 .out = &stdout_writer.interface,
5629 .in = undefined, // won't be receiving messages5633 .in = undefined, // won't be receiving messages
...@@ -5683,19 +5687,23 @@ fn jitCmd(...@@ -5683,19 +5687,23 @@ fn jitCmd(
5683 child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe;5687 child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe;
5684 child.stderr_behavior = .Inherit;5688 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| {5695 if (options.capture) |ptr| {
5689 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});5696 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
5690 ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));5697 ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));
5691 }5698 }
56925699
5693 const term = try child.wait();5700 break :t try child.wait(io);
5701 };
5694 switch (term) {5702 switch (term) {
5695 .Exited => |code| {5703 .Exited => |code| {
5696 if (code == 0) {5704 if (code == 0) {
5697 if (options.capture != null) return;5705 if (options.capture != null) return;
5698 return cleanExit();5706 return cleanExit(io);
5699 }5707 }
5700 const cmd = try std.mem.join(arena, " ", child_argv.items);5708 const cmd = try std.mem.join(arena, " ", child_argv.items);
5701 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });5709 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
...@@ -5818,9 +5826,9 @@ pub fn lldMain(...@@ -5818,9 +5826,9 @@ pub fn lldMain(
5818const ArgIteratorResponseFile = process.ArgIteratorGeneral(.{ .comments = true, .single_quotes = true });5826const ArgIteratorResponseFile = process.ArgIteratorGeneral(.{ .comments = true, .single_quotes = true });
58195827
5820/// Initialize the arguments from a Response File. "*.rsp"5828/// 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 {
5822 const max_bytes = 10 * 1024 * 1024; // 10 MiB of command line arguments is a reasonable limit5830 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));
5824 errdefer allocator.free(cmd_line);5832 errdefer allocator.free(cmd_line);
58255833
5826 return ArgIteratorResponseFile.initTakeOwnership(allocator, cmd_line);5834 return ArgIteratorResponseFile.initTakeOwnership(allocator, cmd_line);
...@@ -5948,7 +5956,7 @@ pub const ClangArgIterator = struct {...@@ -5948,7 +5956,7 @@ pub const ClangArgIterator = struct {
5948 };5956 };
5949 }5957 }
59505958
5951 fn next(self: *ClangArgIterator) !void {5959 fn next(self: *ClangArgIterator, io: Io) !void {
5952 assert(self.has_next);5960 assert(self.has_next);
5953 assert(self.next_index < self.argv.len);5961 assert(self.next_index < self.argv.len);
5954 // In this state we know that the parameter we are looking at is a root parameter5962 // In this state we know that the parameter we are looking at is a root parameter
...@@ -5966,10 +5974,8 @@ pub const ClangArgIterator = struct {...@@ -5966,10 +5974,8 @@ pub const ClangArgIterator = struct {
5966 const arena = self.arena;5974 const arena = self.arena;
5967 const resp_file_path = arg[1..];5975 const resp_file_path = arg[1..];
59685976
5969 self.arg_iterator_response_file =5977 self.arg_iterator_response_file = initArgIteratorResponseFile(arena, io, resp_file_path) catch |err|
5970 initArgIteratorResponseFile(arena, resp_file_path) catch |err| {5978 fatal("unable to read response file '{s}': {t}", .{ resp_file_path, err });
5971 fatal("unable to read response file '{s}': {s}", .{ resp_file_path, @errorName(err) });
5972 };
5973 // NOTE: The ArgIteratorResponseFile returns tokens from next() that are slices of an5979 // NOTE: The ArgIteratorResponseFile returns tokens from next() that are slices of an
5974 // internal buffer. This internal buffer is arena allocated, so it is not cleaned up here.5980 // 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 {...@@ -6156,8 +6162,8 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
6156 const arg = args[i];6162 const arg = args[i];
6157 if (mem.startsWith(u8, arg, "-")) {6163 if (mem.startsWith(u8, arg, "-")) {
6158 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {6164 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
6159 try fs.File.stdout().writeAll(usage_ast_check);6165 try Io.File.stdout().writeStreamingAll(io, usage_ast_check);
6160 return cleanExit();6166 return cleanExit(io);
6161 } else if (mem.eql(u8, arg, "-t")) {6167 } else if (mem.eql(u8, arg, "-t")) {
6162 want_output_text = true;6168 want_output_text = true;
6163 } else if (mem.eql(u8, arg, "--zon")) {6169 } else if (mem.eql(u8, arg, "--zon")) {
...@@ -6184,12 +6190,12 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6184,12 +6190,12 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
6184 const display_path = zig_source_path orelse "<stdin>";6190 const display_path = zig_source_path orelse "<stdin>";
6185 const source: [:0]const u8 = s: {6191 const source: [:0]const u8 = s: {
6186 var f = if (zig_source_path) |p| file: {6192 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| {
6188 fatal("unable to open file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });6194 fatal("unable to open file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });
6189 };6195 };
6190 } else fs.File.stdin();6196 } else Io.File.stdin();
6191 defer if (zig_source_path != null) f.close();6197 defer if (zig_source_path != null) f.close(io);
6192 var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer);6198 var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer);
6193 break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| {6199 break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| {
6194 fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });6200 fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });
6195 };6201 };
...@@ -6207,7 +6213,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6207,7 +6213,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
62076213
6208 const tree = try Ast.parse(arena, source, mode);6214 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);
6211 const stdout_bw = &stdout_writer.interface;6217 const stdout_bw = &stdout_writer.interface;
6212 switch (mode) {6218 switch (mode) {
6213 .zig => {6219 .zig => {
...@@ -6218,7 +6224,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6218,7 +6224,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
6218 try wip_errors.init(arena);6224 try wip_errors.init(arena);
6219 try wip_errors.addZirErrorMessages(zir, tree, source, display_path);6225 try wip_errors.addZirErrorMessages(zir, tree, source, display_path);
6220 var error_bundle = try wip_errors.toOwnedBundle("");6226 var error_bundle = try wip_errors.toOwnedBundle("");
6221 error_bundle.renderToStdErr(.{}, color);6227 try error_bundle.renderToStderr(io, .{}, color);
6222 if (zir.loweringFailed()) {6228 if (zir.loweringFailed()) {
6223 process.exit(1);6229 process.exit(1);
6224 }6230 }
...@@ -6228,7 +6234,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6228,7 +6234,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
6228 if (zir.hasCompileErrors()) {6234 if (zir.hasCompileErrors()) {
6229 process.exit(1);6235 process.exit(1);
6230 } else {6236 } else {
6231 return cleanExit();6237 return cleanExit(io);
6232 }6238 }
6233 }6239 }
6234 if (!build_options.enable_debug_extensions) {6240 if (!build_options.enable_debug_extensions) {
...@@ -6279,7 +6285,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6279,7 +6285,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
6279 if (zir.hasCompileErrors()) {6285 if (zir.hasCompileErrors()) {
6280 process.exit(1);6286 process.exit(1);
6281 } else {6287 } else {
6282 return cleanExit();6288 return cleanExit(io);
6283 }6289 }
6284 },6290 },
6285 .zon => {6291 .zon => {
...@@ -6289,12 +6295,12 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6289,12 +6295,12 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
6289 try wip_errors.init(arena);6295 try wip_errors.init(arena);
6290 try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path);6296 try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path);
6291 var error_bundle = try wip_errors.toOwnedBundle("");6297 var error_bundle = try wip_errors.toOwnedBundle("");
6292 error_bundle.renderToStdErr(.{}, color);6298 error_bundle.renderToStderr(io, .{}, color) catch {};
6293 process.exit(1);6299 process.exit(1);
6294 }6300 }
62956301
6296 if (!want_output_text) {6302 if (!want_output_text) {
6297 return cleanExit();6303 return cleanExit(io);
6298 }6304 }
62996305
6300 if (!build_options.enable_debug_extensions) {6306 if (!build_options.enable_debug_extensions) {
...@@ -6303,7 +6309,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6303,7 +6309,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
63036309
6304 try @import("print_zoir.zig").renderToWriter(zoir, arena, stdout_bw);6310 try @import("print_zoir.zig").renderToWriter(zoir, arena, stdout_bw);
6305 try stdout_bw.flush();6311 try stdout_bw.flush();
6306 return cleanExit();6312 return cleanExit(io);
6307 },6313 },
6308 }6314 }
6309}6315}
...@@ -6330,8 +6336,8 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void {...@@ -6330,8 +6336,8 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void {
6330 const arg = args[i];6336 const arg = args[i];
6331 if (mem.startsWith(u8, arg, "-")) {6337 if (mem.startsWith(u8, arg, "-")) {
6332 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {6338 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
6333 try fs.File.stdout().writeAll(detect_cpu_usage);6339 try Io.File.stdout().writeStreamingAll(io, detect_cpu_usage);
6334 return cleanExit();6340 return cleanExit(io);
6335 } else if (mem.eql(u8, arg, "--llvm")) {6341 } else if (mem.eql(u8, arg, "--llvm")) {
6336 use_llvm = true;6342 use_llvm = true;
6337 } else {6343 } else {
...@@ -6351,10 +6357,10 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void {...@@ -6351,10 +6357,10 @@ fn cmdDetectCpu(io: Io, args: []const []const u8) !void {
6351 const name = llvm.GetHostCPUName() orelse fatal("LLVM could not figure out the host cpu name", .{});6357 const name = llvm.GetHostCPUName() orelse fatal("LLVM could not figure out the host cpu name", .{});
6352 const features = llvm.GetHostCPUFeatures() orelse fatal("LLVM could not figure out the host cpu feature set", .{});6358 const features = llvm.GetHostCPUFeatures() orelse fatal("LLVM could not figure out the host cpu feature set", .{});
6353 const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features);6359 const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features);
6354 try printCpu(cpu);6360 try printCpu(io, cpu);
6355 } else {6361 } else {
6356 const host_target = std.zig.resolveTargetQueryOrFatal(io, .{});6362 const host_target = std.zig.resolveTargetQueryOrFatal(io, .{});
6357 try printCpu(host_target.cpu);6363 try printCpu(io, host_target.cpu);
6358 }6364 }
6359}6365}
63606366
...@@ -6421,8 +6427,8 @@ fn detectNativeCpuWithLLVM(...@@ -6421,8 +6427,8 @@ fn detectNativeCpuWithLLVM(
6421 return result;6427 return result;
6422}6428}
64236429
6424fn printCpu(cpu: std.Target.Cpu) !void {6430fn printCpu(io: Io, cpu: std.Target.Cpu) !void {
6425 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);6431 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
6426 const stdout_bw = &stdout_writer.interface;6432 const stdout_bw = &stdout_writer.interface;
64276433
6428 if (cpu.model.llvm_name) |llvm_name| {6434 if (cpu.model.llvm_name) |llvm_name| {
...@@ -6444,6 +6450,7 @@ fn printCpu(cpu: std.Target.Cpu) !void {...@@ -6444,6 +6450,7 @@ fn printCpu(cpu: std.Target.Cpu) !void {
6444fn cmdDumpLlvmInts(6450fn cmdDumpLlvmInts(
6445 gpa: Allocator,6451 gpa: Allocator,
6446 arena: Allocator,6452 arena: Allocator,
6453 io: Io,
6447 args: []const []const u8,6454 args: []const []const u8,
6448) !void {6455) !void {
6449 dev.check(.llvm_ints_command);6456 dev.check(.llvm_ints_command);
...@@ -6471,7 +6478,7 @@ fn cmdDumpLlvmInts(...@@ -6471,7 +6478,7 @@ fn cmdDumpLlvmInts(
6471 const dl = tm.createTargetDataLayout();6478 const dl = tm.createTargetDataLayout();
6472 const context = llvm.Context.create();6479 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);
6475 const stdout_bw = &stdout_writer.interface;6482 const stdout_bw = &stdout_writer.interface;
6476 for ([_]u16{ 1, 8, 16, 32, 64, 128, 256 }) |bits| {6483 for ([_]u16{ 1, 8, 16, 32, 64, 128, 256 }) |bits| {
6477 const int_type = context.intType(bits);6484 const int_type = context.intType(bits);
...@@ -6480,7 +6487,7 @@ fn cmdDumpLlvmInts(...@@ -6480,7 +6487,7 @@ fn cmdDumpLlvmInts(
6480 }6487 }
6481 try stdout_bw.flush();6488 try stdout_bw.flush();
64826489
6483 return cleanExit();6490 return cleanExit(io);
6484}6491}
64856492
6486/// This is only enabled for debug builds.6493/// This is only enabled for debug builds.
...@@ -6491,13 +6498,13 @@ fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6491,13 +6498,13 @@ fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void {
64916498
6492 const cache_file = args[0];6499 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| {
6495 fatal("unable to open zir cache file for dumping '{s}': {s}", .{ cache_file, @errorName(err) });6502 fatal("unable to open zir cache file for dumping '{s}': {s}", .{ cache_file, @errorName(err) });
6496 };6503 };
6497 defer f.close();6504 defer f.close(io);
64986505
6499 const zir = try Zcu.loadZirCache(arena, io, f);6506 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);
6501 const stdout_bw = &stdout_writer.interface;6508 const stdout_bw = &stdout_writer.interface;
6502 {6509 {
6503 const instruction_bytes = zir.instructions.len *6510 const instruction_bytes = zir.instructions.len *
...@@ -6538,18 +6545,18 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6538,18 +6545,18 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {
6538 const new_source_path = args[1];6545 const new_source_path = args[1];
65396546
6540 const old_source = source: {6547 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|
6542 fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) });6549 fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) });
6543 defer f.close();6550 defer f.close(io);
6544 var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer);6551 var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer);
6545 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|6552 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|
6546 fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) });6553 fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) });
6547 };6554 };
6548 const new_source = source: {6555 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|
6550 fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) });6557 fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) });
6551 defer f.close();6558 defer f.close(io);
6552 var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer);6559 var file_reader: Io.File.Reader = f.reader(io, &stdin_buffer);
6553 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|6560 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|
6554 fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) });6561 fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) });
6555 };6562 };
...@@ -6562,7 +6569,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6562,7 +6569,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {
6562 try wip_errors.init(arena);6569 try wip_errors.init(arena);
6563 try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path);6570 try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path);
6564 var error_bundle = try wip_errors.toOwnedBundle("");6571 var error_bundle = try wip_errors.toOwnedBundle("");
6565 error_bundle.renderToStdErr(.{}, color);6572 error_bundle.renderToStderr(io, .{}, color) catch {};
6566 process.exit(1);6573 process.exit(1);
6567 }6574 }
65686575
...@@ -6574,14 +6581,14 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {...@@ -6574,14 +6581,14 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {
6574 try wip_errors.init(arena);6581 try wip_errors.init(arena);
6575 try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path);6582 try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path);
6576 var error_bundle = try wip_errors.toOwnedBundle("");6583 var error_bundle = try wip_errors.toOwnedBundle("");
6577 error_bundle.renderToStdErr(.{}, color);6584 error_bundle.renderToStderr(io, .{}, color) catch {};
6578 process.exit(1);6585 process.exit(1);
6579 }6586 }
65806587
6581 var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .empty;6588 var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .empty;
6582 try Zcu.mapOldZirToNew(arena, old_zir, new_zir, &inst_map);6589 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);
6585 const stdout_bw = &stdout_writer.interface;6592 const stdout_bw = &stdout_writer.interface;
6586 {6593 {
6587 try stdout_bw.print("Instruction mappings:\n", .{});6594 try stdout_bw.print("Instruction mappings:\n", .{});
...@@ -6623,7 +6630,7 @@ fn warnAboutForeignBinaries(...@@ -6623,7 +6630,7 @@ fn warnAboutForeignBinaries(
6623 const host_query: std.Target.Query = .{};6630 const host_query: std.Target.Query = .{};
6624 const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query);6631 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 })) {
6627 .native => return,6634 .native => return,
6628 .rosetta => {6635 .rosetta => {
6629 const host_name = try host_target.zigTriple(arena);6636 const host_name = try host_target.zigTriple(arena);
...@@ -6829,6 +6836,7 @@ const ClangSearchSanitizer = struct {...@@ -6829,6 +6836,7 @@ const ClangSearchSanitizer = struct {
6829};6836};
68306837
6831fn accessFrameworkPath(6838fn accessFrameworkPath(
6839 io: Io,
6832 test_path: *std.array_list.Managed(u8),6840 test_path: *std.array_list.Managed(u8),
6833 checked_paths: *std.array_list.Managed(u8),6841 checked_paths: *std.array_list.Managed(u8),
6834 framework_dir_path: []const u8,6842 framework_dir_path: []const u8,
...@@ -6842,7 +6850,7 @@ fn accessFrameworkPath(...@@ -6842,7 +6850,7 @@ fn accessFrameworkPath(
6842 framework_dir_path, framework_name, framework_name, ext,6850 framework_dir_path, framework_name, framework_name, ext,
6843 });6851 });
6844 try checked_paths.print("\n {s}", .{test_path.items});6852 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) {
6846 error.FileNotFound => continue,6854 error.FileNotFound => continue,
6847 else => |e| fatal("unable to search for {s} framework '{s}': {s}", .{6855 else => |e| fatal("unable to search for {s} framework '{s}': {s}", .{
6848 ext, test_path.items, @errorName(e),6856 ext, test_path.items, @errorName(e),
...@@ -6912,8 +6920,8 @@ fn cmdFetch(...@@ -6912,8 +6920,8 @@ fn cmdFetch(
6912 const arg = args[i];6920 const arg = args[i];
6913 if (mem.startsWith(u8, arg, "-")) {6921 if (mem.startsWith(u8, arg, "-")) {
6914 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {6922 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
6915 try fs.File.stdout().writeAll(usage_fetch);6923 try Io.File.stdout().writeStreamingAll(io, usage_fetch);
6916 return cleanExit();6924 return cleanExit(io);
6917 } else if (mem.eql(u8, arg, "--global-cache-dir")) {6925 } else if (mem.eql(u8, arg, "--global-cache-dir")) {
6918 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});6926 if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
6919 i += 1;6927 i += 1;
...@@ -6946,7 +6954,7 @@ fn cmdFetch(...@@ -6946,7 +6954,7 @@ fn cmdFetch(
69466954
6947 try http_client.initDefaultProxies(arena);6955 try http_client.initDefaultProxies(arena);
69486956
6949 var root_prog_node = std.Progress.start(.{6957 var root_prog_node = std.Progress.start(io, .{
6950 .root_name = "Fetch",6958 .root_name = "Fetch",
6951 });6959 });
6952 defer root_prog_node.end();6960 defer root_prog_node.end();
...@@ -6954,11 +6962,11 @@ fn cmdFetch(...@@ -6954,11 +6962,11 @@ fn cmdFetch(
6954 var global_cache_directory: Directory = l: {6962 var global_cache_directory: Directory = l: {
6955 const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena);6963 const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena);
6956 break :l .{6964 break :l .{
6957 .handle = try fs.cwd().makeOpenPath(p, .{}),6965 .handle = try Io.Dir.cwd().createDirPathOpen(io, p, .{}),
6958 .path = p,6966 .path = p,
6959 };6967 };
6960 };6968 };
6961 defer global_cache_directory.handle.close();6969 defer global_cache_directory.handle.close(io);
69626970
6963 var job_queue: Package.Fetch.JobQueue = .{6971 var job_queue: Package.Fetch.JobQueue = .{
6964 .io = io,6972 .io = io,
...@@ -7009,7 +7017,7 @@ fn cmdFetch(...@@ -7009,7 +7017,7 @@ fn cmdFetch(
70097017
7010 if (fetch.error_bundle.root_list.items.len > 0) {7018 if (fetch.error_bundle.root_list.items.len > 0) {
7011 var errors = try fetch.error_bundle.toOwnedBundle("");7019 var errors = try fetch.error_bundle.toOwnedBundle("");
7012 errors.renderToStdErr(.{}, color);7020 errors.renderToStderr(io, .{}, color) catch {};
7013 process.exit(1);7021 process.exit(1);
7014 }7022 }
70157023
...@@ -7021,10 +7029,10 @@ fn cmdFetch(...@@ -7021,10 +7029,10 @@ fn cmdFetch(
70217029
7022 const name = switch (save) {7030 const name = switch (save) {
7023 .no => {7031 .no => {
7024 var stdout = fs.File.stdout().writerStreaming(&stdout_buffer);7032 var stdout = Io.File.stdout().writerStreaming(io, &stdout_buffer);
7025 try stdout.interface.print("{s}\n", .{package_hash_slice});7033 try stdout.interface.print("{s}\n", .{package_hash_slice});
7026 try stdout.interface.flush();7034 try stdout.interface.flush();
7027 return cleanExit();7035 return cleanExit(io);
7028 },7036 },
7029 .yes, .exact => |name| name: {7037 .yes, .exact => |name| name: {
7030 if (name) |n| break :name n;7038 if (name) |n| break :name n;
...@@ -7036,14 +7044,14 @@ fn cmdFetch(...@@ -7036,14 +7044,14 @@ fn cmdFetch(
70367044
7037 const cwd_path = try introspect.getResolvedCwd(arena);7045 const cwd_path = try introspect.getResolvedCwd(arena);
70387046
7039 var build_root = try findBuildRoot(arena, .{7047 var build_root = try findBuildRoot(arena, io, .{
7040 .cwd_path = cwd_path,7048 .cwd_path = cwd_path,
7041 });7049 });
7042 defer build_root.deinit();7050 defer build_root.deinit(io);
70437051
7044 // The name to use in case the manifest file needs to be created now.7052 // The name to use in case the manifest file needs to be created now.
7045 const init_root_name = fs.path.basename(build_root.directory.path orelse cwd_path);7053 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, .{
7047 .root_name = try sanitizeExampleName(arena, init_root_name),7055 .root_name = try sanitizeExampleName(arena, init_root_name),
7048 .dir = build_root.directory.handle,7056 .dir = build_root.directory.handle,
7049 .color = color,7057 .color = color,
...@@ -7159,15 +7167,16 @@ fn cmdFetch(...@@ -7159,15 +7167,16 @@ fn cmdFetch(
7159 try ast.render(gpa, &aw.writer, fixups);7167 try ast.render(gpa, &aw.writer, fixups);
7160 const rendered = aw.written();7168 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| {
7163 fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err });7171 fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err });
7164 };7172 };
71657173
7166 return cleanExit();7174 return cleanExit(io);
7167}7175}
71687176
7169fn createEmptyDependenciesModule(7177fn createEmptyDependenciesModule(
7170 arena: Allocator,7178 arena: Allocator,
7179 io: Io,
7171 main_mod: *Package.Module,7180 main_mod: *Package.Module,
7172 dirs: Compilation.Directories,7181 dirs: Compilation.Directories,
7173 global_options: Compilation.Config,7182 global_options: Compilation.Config,
...@@ -7176,6 +7185,7 @@ fn createEmptyDependenciesModule(...@@ -7176,6 +7185,7 @@ fn createEmptyDependenciesModule(
7176 try Package.Fetch.JobQueue.createEmptyDependenciesSource(&source);7185 try Package.Fetch.JobQueue.createEmptyDependenciesSource(&source);
7177 _ = try createDependenciesModule(7186 _ = try createDependenciesModule(
7178 arena,7187 arena,
7188 io,
7179 source.items,7189 source.items,
7180 main_mod,7190 main_mod,
7181 dirs,7191 dirs,
...@@ -7187,6 +7197,7 @@ fn createEmptyDependenciesModule(...@@ -7187,6 +7197,7 @@ fn createEmptyDependenciesModule(
7187/// build runner to obtain via `@import("@dependencies")`.7197/// build runner to obtain via `@import("@dependencies")`.
7188fn createDependenciesModule(7198fn createDependenciesModule(
7189 arena: Allocator,7199 arena: Allocator,
7200 io: Io,
7190 source: []const u8,7201 source: []const u8,
7191 main_mod: *Package.Module,7202 main_mod: *Package.Module,
7192 dirs: Compilation.Directories,7203 dirs: Compilation.Directories,
...@@ -7197,9 +7208,9 @@ fn createDependenciesModule(...@@ -7197,9 +7208,9 @@ fn createDependenciesModule(
7197 const rand_int = std.crypto.random.int(u64);7208 const rand_int = std.crypto.random.int(u64);
7198 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);7209 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
7199 {7210 {
7200 var tmp_dir = try dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{});7211 var tmp_dir = try dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{});
7201 defer tmp_dir.close();7212 defer tmp_dir.close(io);
7202 try tmp_dir.writeFile(.{ .sub_path = basename, .data = source });7213 try tmp_dir.writeFile(io, .{ .sub_path = basename, .data = source });
7203 }7214 }
72047215
7205 var hh: Cache.HashHelper = .{};7216 var hh: Cache.HashHelper = .{};
...@@ -7208,11 +7219,7 @@ fn createDependenciesModule(...@@ -7208,11 +7219,7 @@ fn createDependenciesModule(
7208 const hex_digest = hh.final();7219 const hex_digest = hh.final();
72097220
7210 const o_dir_sub_path = try arena.dupe(u8, "o" ++ fs.path.sep_str ++ hex_digest);7221 const o_dir_sub_path = try arena.dupe(u8, "o" ++ fs.path.sep_str ++ hex_digest);
7211 try Package.Fetch.renameTmpIntoCache(7222 try Package.Fetch.renameTmpIntoCache(io, dirs.local_cache.handle, tmp_dir_sub_path, o_dir_sub_path);
7212 dirs.local_cache.handle,
7213 tmp_dir_sub_path,
7214 o_dir_sub_path,
7215 );
72167223
7217 const deps_mod = try Package.Module.create(arena, .{7224 const deps_mod = try Package.Module.create(arena, .{
7218 .paths = .{7225 .paths = .{
...@@ -7232,10 +7239,10 @@ fn createDependenciesModule(...@@ -7232,10 +7239,10 @@ fn createDependenciesModule(
7232const BuildRoot = struct {7239const BuildRoot = struct {
7233 directory: Cache.Directory,7240 directory: Cache.Directory,
7234 build_zig_basename: []const u8,7241 build_zig_basename: []const u8,
7235 cleanup_build_dir: ?fs.Dir,7242 cleanup_build_dir: ?Io.Dir,
72367243
7237 fn deinit(br: *BuildRoot) void {7244 fn deinit(br: *BuildRoot, io: Io) void {
7238 if (br.cleanup_build_dir) |*dir| dir.close();7245 if (br.cleanup_build_dir) |*dir| dir.close(io);
7239 br.* = undefined;7246 br.* = undefined;
7240 }7247 }
7241};7248};
...@@ -7245,7 +7252,7 @@ const FindBuildRootOptions = struct {...@@ -7245,7 +7252,7 @@ const FindBuildRootOptions = struct {
7245 cwd_path: ?[]const u8 = null,7252 cwd_path: ?[]const u8 = null,
7246};7253};
72477254
7248fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {7255fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !BuildRoot {
7249 const cwd_path = options.cwd_path orelse try introspect.getResolvedCwd(arena);7256 const cwd_path = options.cwd_path orelse try introspect.getResolvedCwd(arena);
7250 const build_zig_basename = if (options.build_file) |bf|7257 const build_zig_basename = if (options.build_file) |bf|
7251 fs.path.basename(bf)7258 fs.path.basename(bf)
...@@ -7254,7 +7261,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {...@@ -7254,7 +7261,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {
72547261
7255 if (options.build_file) |bf| {7262 if (options.build_file) |bf| {
7256 if (fs.path.dirname(bf)) |dirname| {7263 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| {
7258 fatal("unable to open directory to build file from argument 'build-file', '{s}': {s}", .{ dirname, @errorName(err) });7265 fatal("unable to open directory to build file from argument 'build-file', '{s}': {s}", .{ dirname, @errorName(err) });
7259 };7266 };
7260 return .{7267 return .{
...@@ -7266,7 +7273,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {...@@ -7266,7 +7273,7 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {
72667273
7267 return .{7274 return .{
7268 .build_zig_basename = build_zig_basename,7275 .build_zig_basename = build_zig_basename,
7269 .directory = .{ .path = null, .handle = fs.cwd() },7276 .directory = .{ .path = null, .handle = Io.Dir.cwd() },
7270 .cleanup_build_dir = null,7277 .cleanup_build_dir = null,
7271 };7278 };
7272 }7279 }
...@@ -7274,8 +7281,8 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {...@@ -7274,8 +7281,8 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {
7274 var dirname: []const u8 = cwd_path;7281 var dirname: []const u8 = cwd_path;
7275 while (true) {7282 while (true) {
7276 const joined_path = try fs.path.join(arena, &[_][]const u8{ dirname, build_zig_basename });7283 const joined_path = try fs.path.join(arena, &[_][]const u8{ dirname, build_zig_basename });
7277 if (fs.cwd().access(joined_path, .{})) |_| {7284 if (Io.Dir.cwd().access(io, joined_path, .{})) |_| {
7278 const dir = fs.cwd().openDir(dirname, .{}) catch |err| {7285 const dir = Io.Dir.cwd().openDir(io, dirname, .{}) catch |err| {
7279 fatal("unable to open directory while searching for build.zig file, '{s}': {s}", .{ dirname, @errorName(err) });7286 fatal("unable to open directory while searching for build.zig file, '{s}': {s}", .{ dirname, @errorName(err) });
7280 };7287 };
7281 return .{7288 return .{
...@@ -7304,17 +7311,19 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {...@@ -7304,17 +7311,19 @@ fn findBuildRoot(arena: Allocator, options: FindBuildRootOptions) !BuildRoot {
73047311
7305const LoadManifestOptions = struct {7312const LoadManifestOptions = struct {
7306 root_name: []const u8,7313 root_name: []const u8,
7307 dir: fs.Dir,7314 dir: Io.Dir,
7308 color: Color,7315 color: Color,
7309};7316};
73107317
7311fn loadManifest(7318fn loadManifest(
7312 gpa: Allocator,7319 gpa: Allocator,
7313 arena: Allocator,7320 arena: Allocator,
7321 io: Io,
7314 options: LoadManifestOptions,7322 options: LoadManifestOptions,
7315) !struct { Package.Manifest, Ast } {7323) !struct { Package.Manifest, Ast } {
7316 const manifest_bytes = while (true) {7324 const manifest_bytes = while (true) {
7317 break options.dir.readFileAllocOptions(7325 break options.dir.readFileAllocOptions(
7326 io,
7318 Package.Manifest.basename,7327 Package.Manifest.basename,
7319 arena,7328 arena,
7320 .limited(Package.Manifest.max_bytes),7329 .limited(Package.Manifest.max_bytes),
...@@ -7322,7 +7331,7 @@ fn loadManifest(...@@ -7322,7 +7331,7 @@ fn loadManifest(
7322 0,7331 0,
7323 ) catch |err| switch (err) {7332 ) catch |err| switch (err) {
7324 error.FileNotFound => {7333 error.FileNotFound => {
7325 writeSimpleTemplateFile(Package.Manifest.basename,7334 writeSimpleTemplateFile(io, Package.Manifest.basename,
7326 \\.{{7335 \\.{{
7327 \\ .name = .{s},7336 \\ .name = .{s},
7328 \\ .version = "{s}",7337 \\ .version = "{s}",
...@@ -7348,7 +7357,7 @@ fn loadManifest(...@@ -7348,7 +7357,7 @@ fn loadManifest(
7348 errdefer ast.deinit(gpa);7357 errdefer ast.deinit(gpa);
73497358
7350 if (ast.errors.len > 0) {7359 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);
7352 process.exit(2);7361 process.exit(2);
7353 }7362 }
73547363
...@@ -7365,7 +7374,7 @@ fn loadManifest(...@@ -7365,7 +7374,7 @@ fn loadManifest(
73657374
7366 var error_bundle = try wip_errors.toOwnedBundle("");7375 var error_bundle = try wip_errors.toOwnedBundle("");
7367 defer error_bundle.deinit(gpa);7376 defer error_bundle.deinit(gpa);
7368 error_bundle.renderToStdErr(.{}, options.color);7377 error_bundle.renderToStderr(io, .{}, options.color) catch {};
73697378
7370 process.exit(2);7379 process.exit(2);
7371 }7380 }
...@@ -7374,12 +7383,12 @@ fn loadManifest(...@@ -7374,12 +7383,12 @@ fn loadManifest(
73747383
7375const Templates = struct {7384const Templates = struct {
7376 zig_lib_directory: Cache.Directory,7385 zig_lib_directory: Cache.Directory,
7377 dir: fs.Dir,7386 dir: Io.Dir,
7378 buffer: std.array_list.Managed(u8),7387 buffer: std.array_list.Managed(u8),
73797388
7380 fn deinit(templates: *Templates) void {7389 fn deinit(templates: *Templates, io: Io) void {
7381 templates.zig_lib_directory.handle.close();7390 templates.zig_lib_directory.handle.close(io);
7382 templates.dir.close();7391 templates.dir.close(io);
7383 templates.buffer.deinit();7392 templates.buffer.deinit();
7384 templates.* = undefined;7393 templates.* = undefined;
7385 }7394 }
...@@ -7387,20 +7396,21 @@ const Templates = struct {...@@ -7387,20 +7396,21 @@ const Templates = struct {
7387 fn write(7396 fn write(
7388 templates: *Templates,7397 templates: *Templates,
7389 arena: Allocator,7398 arena: Allocator,
7390 out_dir: fs.Dir,7399 io: Io,
7400 out_dir: Io.Dir,
7391 root_name: []const u8,7401 root_name: []const u8,
7392 template_path: []const u8,7402 template_path: []const u8,
7393 fingerprint: Package.Fingerprint,7403 fingerprint: Package.Fingerprint,
7394 ) !void {7404 ) !void {
7395 if (fs.path.dirname(template_path)) |dirname| {7405 if (fs.path.dirname(template_path)) |dirname| {
7396 out_dir.makePath(dirname) catch |err| {7406 out_dir.createDirPath(io, dirname) catch |err| {
7397 fatal("unable to make path '{s}': {s}", .{ dirname, @errorName(err) });7407 fatal("unable to make path '{s}': {t}", .{ dirname, err });
7398 };7408 };
7399 }7409 }
74007410
7401 const max_bytes = 10 * 1024 * 1024;7411 const max_bytes = 10 * 1024 * 1024;
7402 const contents = templates.dir.readFileAlloc(template_path, arena, .limited(max_bytes)) catch |err| {7412 const contents = templates.dir.readFileAlloc(io, template_path, arena, .limited(max_bytes)) catch |err| {
7403 fatal("unable to read template file '{s}': {s}", .{ template_path, @errorName(err) });7413 fatal("unable to read template file '{s}': {t}", .{ template_path, err });
7404 };7414 };
7405 templates.buffer.clearRetainingCapacity();7415 templates.buffer.clearRetainingCapacity();
7406 try templates.buffer.ensureUnusedCapacity(contents.len);7416 try templates.buffer.ensureUnusedCapacity(contents.len);
...@@ -7428,39 +7438,39 @@ const Templates = struct {...@@ -7428,39 +7438,39 @@ const Templates = struct {
7428 i += 1;7438 i += 1;
7429 }7439 }
74307440
7431 return out_dir.writeFile(.{7441 return out_dir.writeFile(io, .{
7432 .sub_path = template_path,7442 .sub_path = template_path,
7433 .data = templates.buffer.items,7443 .data = templates.buffer.items,
7434 .flags = .{ .exclusive = true },7444 .flags = .{ .exclusive = true },
7435 });7445 });
7436 }7446 }
7437};7447};
7438fn writeSimpleTemplateFile(file_name: []const u8, comptime fmt: []const u8, args: anytype) !void {7448fn writeSimpleTemplateFile(io: Io, file_name: []const u8, comptime fmt: []const u8, args: anytype) !void {
7439 const f = try fs.cwd().createFile(file_name, .{ .exclusive = true });7449 const f = try Io.Dir.cwd().createFile(io, file_name, .{ .exclusive = true });
7440 defer f.close();7450 defer f.close(io);
7441 var buf: [4096]u8 = undefined;7451 var buf: [4096]u8 = undefined;
7442 var fw = f.writer(&buf);7452 var fw = f.writer(io, &buf);
7443 try fw.interface.print(fmt, args);7453 try fw.interface.print(fmt, args);
7444 try fw.interface.flush();7454 try fw.interface.flush();
7445}7455}
74467456
7447fn findTemplates(gpa: Allocator, arena: Allocator) Templates {7457fn findTemplates(gpa: Allocator, arena: Allocator, io: Io) Templates {
7448 const cwd_path = introspect.getResolvedCwd(arena) catch |err| {7458 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});
7450 };7460 };
7451 const self_exe_path = fs.selfExePathAlloc(arena) catch |err| {7461 const self_exe_path = process.executablePathAlloc(io, arena) catch |err| {
7452 fatal("unable to find self exe path: {s}", .{@errorName(err)});7462 fatal("unable to find self exe path: {t}", .{err});
7453 };7463 };
7454 var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, cwd_path, self_exe_path) catch |err| {7464 var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, io, cwd_path, self_exe_path) catch |err| {
7455 fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) });7465 fatal("unable to find zig installation directory '{s}': {t}", .{ self_exe_path, err });
7456 };7466 };
74577467
7458 const s = fs.path.sep_str;7468 const s = fs.path.sep_str;
7459 const template_sub_path = "init";7469 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| {
7461 const path = zig_lib_directory.path orelse ".";7471 const path = zig_lib_directory.path orelse ".";
7462 fatal("unable to open zig project template directory '{s}{s}{s}': {s}", .{7472 fatal("unable to open zig project template directory '{s}{s}{s}': {t}", .{
7463 path, s, template_sub_path, @errorName(err),7473 path, s, template_sub_path, err,
7464 });7474 });
7465 };7475 };
74667476
...@@ -7574,17 +7584,18 @@ fn anyObjectLinkInputs(link_inputs: []const link.UnresolvedInput) bool {...@@ -7574,17 +7584,18 @@ fn anyObjectLinkInputs(link_inputs: []const link.UnresolvedInput) bool {
7574 return false;7584 return false;
7575}7585}
75767586
7577fn addLibDirectoryWarn(lib_directories: *std.ArrayList(Directory), path: []const u8) void {7587fn addLibDirectoryWarn(io: Io, lib_directories: *std.ArrayList(Directory), path: []const u8) void {
7578 return addLibDirectoryWarn2(lib_directories, path, false);7588 return addLibDirectoryWarn2(io, lib_directories, path, false);
7579}7589}
75807590
7581fn addLibDirectoryWarn2(7591fn addLibDirectoryWarn2(
7592 io: Io,
7582 lib_directories: *std.ArrayList(Directory),7593 lib_directories: *std.ArrayList(Directory),
7583 path: []const u8,7594 path: []const u8,
7584 ignore_not_found: bool,7595 ignore_not_found: bool,
7585) void {7596) void {
7586 lib_directories.appendAssumeCapacity(.{7597 lib_directories.appendAssumeCapacity(.{
7587 .handle = fs.cwd().openDir(path, .{}) catch |err| {7598 .handle = Io.Dir.cwd().openDir(io, path, .{}) catch |err| {
7588 if (err == error.FileNotFound and ignore_not_found) return;7599 if (err == error.FileNotFound and ignore_not_found) return;
7589 warn("unable to open library directory '{s}': {s}", .{ path, @errorName(err) });7600 warn("unable to open library directory '{s}': {s}", .{ path, @errorName(err) });
7590 return;7601 return;
src/print_env.zig+11-6
...@@ -1,13 +1,17 @@...@@ -1,13 +1,17 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
3const build_options = @import("build_options");2
4const Compilation = @import("Compilation.zig");3const std = @import("std");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;5const Allocator = std.mem.Allocator;
6const EnvVar = std.zig.EnvVar;6const EnvVar = std.zig.EnvVar;
7const fatal = std.process.fatal;7const fatal = std.process.fatal;
88
9const build_options = @import("build_options");
10const Compilation = @import("Compilation.zig");
11
9pub fn cmdEnv(12pub fn cmdEnv(
10 arena: Allocator,13 arena: Allocator,
14 io: Io,
11 out: *std.Io.Writer,15 out: *std.Io.Writer,
12 args: []const []const u8,16 args: []const []const u8,
13 wasi_preopens: switch (builtin.target.os.tag) {17 wasi_preopens: switch (builtin.target.os.tag) {
...@@ -21,20 +25,21 @@ pub fn cmdEnv(...@@ -21,20 +25,21 @@ pub fn cmdEnv(
2125
22 const self_exe_path = switch (builtin.target.os.tag) {26 const self_exe_path = switch (builtin.target.os.tag) {
23 .wasi => args[0],27 .wasi => args[0],
24 else => std.fs.selfExePathAlloc(arena) catch |err| {28 else => std.process.executablePathAlloc(io, arena) catch |err| {
25 fatal("unable to find zig self exe path: {s}", .{@errorName(err)});29 fatal("unable to find zig self exe path: {t}", .{err});
26 },30 },
27 };31 };
2832
29 var dirs: Compilation.Directories = .init(33 var dirs: Compilation.Directories = .init(
30 arena,34 arena,
35 io,
31 override_lib_dir,36 override_lib_dir,
32 override_global_cache_dir,37 override_global_cache_dir,
33 .global,38 .global,
34 if (builtin.target.os.tag == .wasi) wasi_preopens,39 if (builtin.target.os.tag == .wasi) wasi_preopens,
35 if (builtin.target.os.tag != .wasi) self_exe_path,40 if (builtin.target.os.tag != .wasi) self_exe_path,
36 );41 );
37 defer dirs.deinit();42 defer dirs.deinit(io);
3843
39 const zig_lib_dir = dirs.zig_lib.path orelse "";44 const zig_lib_dir = dirs.zig_lib.path orelse "";
40 const zig_std_dir = try dirs.zig_lib.join(arena, &.{"std"});45 const zig_std_dir = try dirs.zig_lib.join(arena, &.{"std"});
src/print_targets.zig+10-9
...@@ -1,35 +1,38 @@...@@ -1,35 +1,38 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const fs = std.fs;3const fs = std.fs;
3const mem = std.mem;4const mem = std.mem;
4const meta = std.meta;5const meta = std.meta;
5const fatal = std.process.fatal;6const fatal = std.process.fatal;
6const Allocator = std.mem.Allocator;7const Allocator = std.mem.Allocator;
7const Target = std.Target;8const Target = std.Target;
8const target = @import("target.zig");
9const assert = std.debug.assert;9const assert = std.debug.assert;
10
10const glibc = @import("libs/glibc.zig");11const glibc = @import("libs/glibc.zig");
11const introspect = @import("introspect.zig");12const introspect = @import("introspect.zig");
13const target = @import("target.zig");
1214
13pub fn cmdTargets(15pub fn cmdTargets(
14 allocator: Allocator,16 allocator: Allocator,
17 io: Io,
15 args: []const []const u8,18 args: []const []const u8,
16 out: *std.Io.Writer,19 out: *std.Io.Writer,
17 native_target: *const Target,20 native_target: *const Target,
18) !void {21) !void {
19 _ = args;22 _ = args;
20 var zig_lib_directory = introspect.findZigLibDir(allocator) catch |err| {23 var zig_lib_directory = introspect.findZigLibDir(allocator, io) catch |err|
21 fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)});24 fatal("unable to find zig installation directory: {t}", .{err});
22 };25 defer zig_lib_directory.handle.close(io);
23 defer zig_lib_directory.handle.close();
24 defer allocator.free(zig_lib_directory.path.?);26 defer allocator.free(zig_lib_directory.path.?);
2527
26 const abilists_contents = zig_lib_directory.handle.readFileAlloc(28 const abilists_contents = zig_lib_directory.handle.readFileAlloc(
29 io,
27 glibc.abilists_path,30 glibc.abilists_path,
28 allocator,31 allocator,
29 .limited(glibc.abilists_max_size),32 .limited(glibc.abilists_max_size),
30 ) catch |err| switch (err) {33 ) catch |err| switch (err) {
31 error.OutOfMemory => return error.OutOfMemory,34 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}),
33 };36 };
34 defer allocator.free(abilists_contents);37 defer allocator.free(abilists_contents);
3538
...@@ -48,9 +51,7 @@ pub fn cmdTargets(...@@ -48,9 +51,7 @@ pub fn cmdTargets(
48 {51 {
49 var libc_obj = try root_obj.beginTupleField("libc", .{});52 var libc_obj = try root_obj.beginTupleField("libc", .{});
50 for (std.zig.target.available_libcs) |libc| {53 for (std.zig.target.available_libcs) |libc| {
51 const tmp = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{54 const tmp = try std.fmt.allocPrint(allocator, "{t}-{t}-{t}", .{ libc.arch, libc.os, libc.abi });
52 @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi),
53 });
54 defer allocator.free(tmp);55 defer allocator.free(tmp);
55 try libc_obj.field(tmp, .{});56 try libc_obj.field(tmp, .{});
56 }57 }
stage1/wasi.c+61
...@@ -939,6 +939,58 @@ uint32_t wasi_snapshot_preview1_path_remove_directory(uint32_t fd, uint32_t path...@@ -939,6 +939,58 @@ uint32_t wasi_snapshot_preview1_path_remove_directory(uint32_t fd, uint32_t path
939 return wasi_errno_success;939 return wasi_errno_success;
940}940}
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
942uint32_t wasi_snapshot_preview1_path_unlink_file(uint32_t fd, uint32_t path, uint32_t path_len) {994uint32_t wasi_snapshot_preview1_path_unlink_file(uint32_t fd, uint32_t path, uint32_t path_len) {
943 uint8_t *const m = *wasm_memory;995 uint8_t *const m = *wasm_memory;
944 const char *path_ptr = (const char *)&m[path];996 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_...@@ -1038,6 +1090,15 @@ uint32_t wasi_snapshot_preview1_fd_seek(uint32_t fd, uint64_t in_offset, uint32_
1038 return wasi_errno_success;1090 return wasi_errno_success;
1039}1091}
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
1041uint32_t wasi_snapshot_preview1_poll_oneoff(uint32_t in, uint32_t out, uint32_t nsubscriptions, uint32_t res_nevents) {1102uint32_t wasi_snapshot_preview1_poll_oneoff(uint32_t in, uint32_t out, uint32_t nsubscriptions, uint32_t res_nevents) {
1042 (void)in;1103 (void)in;
1043 (void)out;1104 (void)out;
test/cases/disable_stack_tracing.zig+3-3
...@@ -5,16 +5,16 @@ pub const std_options: std.Options = .{...@@ -5,16 +5,16 @@ pub const std_options: std.Options = .{
5pub fn main() !void {5pub fn main() !void {
6 var st_buf: [8]usize = undefined;6 var st_buf: [8]usize = undefined;
7 var buf: [1024]u8 = undefined;7 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
10 const captured_st = try foo(&stdout.interface, &st_buf);10 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 });
12 try stdout.interface.print("stack trace index: {d}\n", .{captured_st.index});12 try stdout.interface.print("stack trace index: {d}\n", .{captured_st.index});
1313
14 try stdout.interface.flush();14 try stdout.interface.flush();
15}15}
16fn foo(w: *std.Io.Writer, st_buf: []usize) !std.builtin.StackTrace {16fn 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 });
18 return std.debug.captureCurrentStackTrace(.{}, st_buf);18 return std.debug.captureCurrentStackTrace(.{}, st_buf);
19}19}
2020
test/incremental/add_decl+13-7
...@@ -7,57 +7,63 @@...@@ -7,57 +7,63 @@
7#file=main.zig7#file=main.zig
8const std = @import("std");8const std = @import("std");
9pub fn main() !void {9pub fn main() !void {
10 try std.fs.File.stdout().writeAll(foo);10 try std.Io.File.stdout().writeStreamingAll(io, foo);
11}11}
12const foo = "good morning\n";12const foo = "good morning\n";
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
13#expect_stdout="good morning\n"14#expect_stdout="good morning\n"
1415
15#update=add new declaration16#update=add new declaration
16#file=main.zig17#file=main.zig
17const std = @import("std");18const std = @import("std");
18pub fn main() !void {19pub fn main() !void {
19 try std.fs.File.stdout().writeAll(foo);20 try std.Io.File.stdout().writeStreamingAll(io, foo);
20}21}
21const foo = "good morning\n";22const foo = "good morning\n";
22const bar = "good evening\n";23const bar = "good evening\n";
24const io = std.Io.Threaded.global_single_threaded.ioBasic();
23#expect_stdout="good morning\n"25#expect_stdout="good morning\n"
2426
25#update=reference new declaration27#update=reference new declaration
26#file=main.zig28#file=main.zig
27const std = @import("std");29const std = @import("std");
28pub fn main() !void {30pub fn main() !void {
29 try std.fs.File.stdout().writeAll(bar);31 try std.Io.File.stdout().writeStreamingAll(io, bar);
30}32}
31const foo = "good morning\n";33const foo = "good morning\n";
32const bar = "good evening\n";34const bar = "good evening\n";
35const io = std.Io.Threaded.global_single_threaded.ioBasic();
33#expect_stdout="good evening\n"36#expect_stdout="good evening\n"
3437
35#update=reference missing declaration38#update=reference missing declaration
36#file=main.zig39#file=main.zig
37const std = @import("std");40const std = @import("std");
38pub fn main() !void {41pub fn main() !void {
39 try std.fs.File.stdout().writeAll(qux);42 try std.Io.File.stdout().writeStreamingAll(io, qux);
40}43}
41const foo = "good morning\n";44const foo = "good morning\n";
42const bar = "good evening\n";45const 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
45#update=add missing declaration49#update=add missing declaration
46#file=main.zig50#file=main.zig
47const std = @import("std");51const std = @import("std");
48pub fn main() !void {52pub fn main() !void {
49 try std.fs.File.stdout().writeAll(qux);53 try std.Io.File.stdout().writeStreamingAll(io, qux);
50}54}
51const foo = "good morning\n";55const foo = "good morning\n";
52const bar = "good evening\n";56const bar = "good evening\n";
53const qux = "good night\n";57const qux = "good night\n";
58const io = std.Io.Threaded.global_single_threaded.ioBasic();
54#expect_stdout="good night\n"59#expect_stdout="good night\n"
5560
56#update=remove unused declarations61#update=remove unused declarations
57#file=main.zig62#file=main.zig
58const std = @import("std");63const std = @import("std");
59pub fn main() !void {64pub fn main() !void {
60 try std.fs.File.stdout().writeAll(qux);65 try std.Io.File.stdout().writeStreamingAll(io, qux);
61}66}
62const qux = "good night\n";67const qux = "good night\n";
68const io = std.Io.Threaded.global_single_threaded.ioBasic();
63#expect_stdout="good night\n"69#expect_stdout="good night\n"
test/incremental/add_decl_namespaced+13-7
...@@ -7,58 +7,64 @@...@@ -7,58 +7,64 @@
7#file=main.zig7#file=main.zig
8const std = @import("std");8const std = @import("std");
9pub fn main() !void {9pub fn main() !void {
10 try std.fs.File.stdout().writeAll(@This().foo);10 try std.Io.File.stdout().writeStreamingAll(io, @This().foo);
11}11}
12const foo = "good morning\n";12const foo = "good morning\n";
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
13#expect_stdout="good morning\n"14#expect_stdout="good morning\n"
1415
15#update=add new declaration16#update=add new declaration
16#file=main.zig17#file=main.zig
17const std = @import("std");18const std = @import("std");
18pub fn main() !void {19pub fn main() !void {
19 try std.fs.File.stdout().writeAll(@This().foo);20 try std.Io.File.stdout().writeStreamingAll(io, @This().foo);
20}21}
21const foo = "good morning\n";22const foo = "good morning\n";
22const bar = "good evening\n";23const bar = "good evening\n";
24const io = std.Io.Threaded.global_single_threaded.ioBasic();
23#expect_stdout="good morning\n"25#expect_stdout="good morning\n"
2426
25#update=reference new declaration27#update=reference new declaration
26#file=main.zig28#file=main.zig
27const std = @import("std");29const std = @import("std");
28pub fn main() !void {30pub fn main() !void {
29 try std.fs.File.stdout().writeAll(@This().bar);31 try std.Io.File.stdout().writeStreamingAll(io, @This().bar);
30}32}
31const foo = "good morning\n";33const foo = "good morning\n";
32const bar = "good evening\n";34const bar = "good evening\n";
35const io = std.Io.Threaded.global_single_threaded.ioBasic();
33#expect_stdout="good evening\n"36#expect_stdout="good evening\n"
3437
35#update=reference missing declaration38#update=reference missing declaration
36#file=main.zig39#file=main.zig
37const std = @import("std");40const std = @import("std");
38pub fn main() !void {41pub fn main() !void {
39 try std.fs.File.stdout().writeAll(@This().qux);42 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
40}43}
41const foo = "good morning\n";44const foo = "good morning\n";
42const bar = "good evening\n";45const 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'
44#expect_error=main.zig:1:1: note: struct declared here48#expect_error=main.zig:1:1: note: struct declared here
4549
46#update=add missing declaration50#update=add missing declaration
47#file=main.zig51#file=main.zig
48const std = @import("std");52const std = @import("std");
49pub fn main() !void {53pub fn main() !void {
50 try std.fs.File.stdout().writeAll(@This().qux);54 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
51}55}
52const foo = "good morning\n";56const foo = "good morning\n";
53const bar = "good evening\n";57const bar = "good evening\n";
54const qux = "good night\n";58const qux = "good night\n";
59const io = std.Io.Threaded.global_single_threaded.ioBasic();
55#expect_stdout="good night\n"60#expect_stdout="good night\n"
5661
57#update=remove unused declarations62#update=remove unused declarations
58#file=main.zig63#file=main.zig
59const std = @import("std");64const std = @import("std");
60pub fn main() !void {65pub fn main() !void {
61 try std.fs.File.stdout().writeAll(@This().qux);66 try std.Io.File.stdout().writeStreamingAll(io, @This().qux);
62}67}
63const qux = "good night\n";68const qux = "good night\n";
69const io = std.Io.Threaded.global_single_threaded.ioBasic();
64#expect_stdout="good night\n"70#expect_stdout="good night\n"
test/incremental/bad_import+4-2
...@@ -8,9 +8,10 @@...@@ -8,9 +8,10 @@
8#file=main.zig8#file=main.zig
9pub fn main() !void {9pub fn main() !void {
10 _ = @import("foo.zig");10 _ = @import("foo.zig");
11 try std.fs.File.stdout().writeAll("success\n");11 try std.Io.File.stdout().writeStreamingAll(io, "success\n");
12}12}
13const std = @import("std");13const std = @import("std");
14const io = std.Io.Threaded.global_single_threaded.ioBasic();
14#file=foo.zig15#file=foo.zig
15comptime {16comptime {
16 _ = @import("bad.zig");17 _ = @import("bad.zig");
...@@ -30,7 +31,8 @@ comptime {...@@ -30,7 +31,8 @@ comptime {
30#file=main.zig31#file=main.zig
31pub fn main() !void {32pub fn main() !void {
32 //_ = @import("foo.zig");33 //_ = @import("foo.zig");
33 try std.fs.File.stdout().writeAll("success\n");34 try std.Io.File.stdout().writeStreamingAll(io, "success\n");
34}35}
35const std = @import("std");36const std = @import("std");
37const io = std.Io.Threaded.global_single_threaded.ioBasic();
36#expect_stdout="success\n"38#expect_stdout="success\n"
test/incremental/change_embed_file+6-3
...@@ -8,8 +8,9 @@...@@ -8,8 +8,9 @@
8const std = @import("std");8const std = @import("std");
9const string = @embedFile("string.txt");9const string = @embedFile("string.txt");
10pub fn main() !void {10pub fn main() !void {
11 try std.fs.File.stdout().writeAll(string);11 try std.Io.File.stdout().writeStreamingAll(io, string);
12}12}
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
13#file=string.txt14#file=string.txt
14Hello, World!15Hello, World!
15#expect_stdout="Hello, World!\n"16#expect_stdout="Hello, World!\n"
...@@ -28,8 +29,9 @@ Hello again, World!...@@ -28,8 +29,9 @@ Hello again, World!
28const std = @import("std");29const std = @import("std");
29const string = @embedFile("string.txt");30const string = @embedFile("string.txt");
30pub fn main() !void {31pub 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");
32}33}
34const io = std.Io.Threaded.global_single_threaded.ioBasic();
33#expect_stdout="a hardcoded string\n"35#expect_stdout="a hardcoded string\n"
3436
35#update=re-introduce reference to file37#update=re-introduce reference to file
...@@ -37,8 +39,9 @@ pub fn main() !void {...@@ -37,8 +39,9 @@ pub fn main() !void {
37const std = @import("std");39const std = @import("std");
38const string = @embedFile("string.txt");40const string = @embedFile("string.txt");
39pub fn main() !void {41pub fn main() !void {
40 try std.fs.File.stdout().writeAll(string);42 try std.Io.File.stdout().writeStreamingAll(io, string);
41}43}
44const io = std.Io.Threaded.global_single_threaded.ioBasic();
42#expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound45#expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound
4346
44#update=recreate file47#update=recreate file
test/incremental/change_enum_tag_type+6-3
...@@ -15,10 +15,11 @@ const Foo = enum(Tag) {...@@ -15,10 +15,11 @@ const Foo = enum(Tag) {
15pub fn main() !void {15pub fn main() !void {
16 var val: Foo = undefined;16 var val: Foo = undefined;
17 val = .a;17 val = .a;
18 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});18 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
19 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});19 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
20}20}
21const std = @import("std");21const std = @import("std");
22const io = std.Io.Threaded.global_single_threaded.ioBasic();
22#expect_stdout="a\n"23#expect_stdout="a\n"
23#update=too many enum fields24#update=too many enum fields
24#file=main.zig25#file=main.zig
...@@ -33,7 +34,7 @@ const Foo = enum(Tag) {...@@ -33,7 +34,7 @@ const Foo = enum(Tag) {
33pub fn main() !void {34pub fn main() !void {
34 var val: Foo = undefined;35 var val: Foo = undefined;
35 val = .a;36 val = .a;
36 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});37 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
37 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});38 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
38}39}
39comptime {40comptime {
...@@ -42,6 +43,7 @@ comptime {...@@ -42,6 +43,7 @@ comptime {
42 std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag);43 std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag);
43}44}
44const std = @import("std");45const std = @import("std");
46const io = std.Io.Threaded.global_single_threaded.ioBasic();
45#expect_error=main.zig:7:5: error: enumeration value '4' too large for type 'u2'47#expect_error=main.zig:7:5: error: enumeration value '4' too large for type 'u2'
46#update=increase tag size48#update=increase tag size
47#file=main.zig49#file=main.zig
...@@ -56,8 +58,9 @@ const Foo = enum(Tag) {...@@ -56,8 +58,9 @@ const Foo = enum(Tag) {
56pub fn main() !void {58pub fn main() !void {
57 var val: Foo = undefined;59 var val: Foo = undefined;
58 val = .a;60 val = .a;
59 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});61 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
60 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});62 try stdout_writer.interface.print("{s}\n", .{@tagName(val)});
61}63}
62const std = @import("std");64const std = @import("std");
65const io = std.Io.Threaded.global_single_threaded.ioBasic();
63#expect_stdout="a\n"66#expect_stdout="a\n"
test/incremental/change_exports+12-6
...@@ -17,10 +17,11 @@ pub fn main() !void {...@@ -17,10 +17,11 @@ pub fn main() !void {
17 extern const bar: u32;17 extern const bar: u32;
18 };18 };
19 S.foo();19 S.foo();
20 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});20 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
21 try stdout_writer.interface.print("{}\n", .{S.bar});21 try stdout_writer.interface.print("{}\n", .{S.bar});
22}22}
23const std = @import("std");23const std = @import("std");
24const io = std.Io.Threaded.global_single_threaded.ioBasic();
24#expect_stdout="123\n"25#expect_stdout="123\n"
2526
26#update=add conflict27#update=add conflict
...@@ -39,10 +40,11 @@ pub fn main() !void {...@@ -39,10 +40,11 @@ pub fn main() !void {
39 extern const other: u32;40 extern const other: u32;
40 };41 };
41 S.foo();42 S.foo();
42 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});43 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
43 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });44 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
44}45}
45const std = @import("std");46const std = @import("std");
47const io = std.Io.Threaded.global_single_threaded.ioBasic();
46#expect_error=main.zig:6:5: error: exported symbol collision: foo48#expect_error=main.zig:6:5: error: exported symbol collision: foo
47#expect_error=main.zig:1:1: note: other symbol here49#expect_error=main.zig:1:1: note: other symbol here
4850
...@@ -62,10 +64,11 @@ pub fn main() !void {...@@ -62,10 +64,11 @@ pub fn main() !void {
62 extern const other: u32;64 extern const other: u32;
63 };65 };
64 S.foo();66 S.foo();
65 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});67 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
66 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });68 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
67}69}
68const std = @import("std");70const std = @import("std");
71const io = std.Io.Threaded.global_single_threaded.ioBasic();
69#expect_stdout="123 456\n"72#expect_stdout="123 456\n"
7073
71#update=put exports in decl74#update=put exports in decl
...@@ -87,10 +90,11 @@ pub fn main() !void {...@@ -87,10 +90,11 @@ pub fn main() !void {
87 extern const other: u32;90 extern const other: u32;
88 };91 };
89 S.foo();92 S.foo();
90 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});93 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
91 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });94 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
92}95}
93const std = @import("std");96const std = @import("std");
97const io = std.Io.Threaded.global_single_threaded.ioBasic();
94#expect_stdout="123 456\n"98#expect_stdout="123 456\n"
9599
96#update=remove reference to exporting decl100#update=remove reference to exporting decl
...@@ -133,10 +137,11 @@ pub fn main() !void {...@@ -133,10 +137,11 @@ pub fn main() !void {
133 extern const other: u32;137 extern const other: u32;
134 };138 };
135 S.foo();139 S.foo();
136 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});140 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
137 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });141 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
138}142}
139const std = @import("std");143const std = @import("std");
144const io = std.Io.Threaded.global_single_threaded.ioBasic();
140#expect_stdout="123 456\n"145#expect_stdout="123 456\n"
141146
142#update=reintroduce reference to exporting decl, introducing conflict147#update=reintroduce reference to exporting decl, introducing conflict
...@@ -158,10 +163,11 @@ pub fn main() !void {...@@ -158,10 +163,11 @@ pub fn main() !void {
158 extern const other: u32;163 extern const other: u32;
159 };164 };
160 S.foo();165 S.foo();
161 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});166 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
162 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });167 try stdout_writer.interface.print("{} {}\n", .{ S.bar, S.other });
163}168}
164const std = @import("std");169const std = @import("std");
170const io = std.Io.Threaded.global_single_threaded.ioBasic();
165#expect_error=main.zig:5:5: error: exported symbol collision: bar171#expect_error=main.zig:5:5: error: exported symbol collision: bar
166#expect_error=main.zig:2:1: note: other symbol here172#expect_error=main.zig:2:1: note: other symbol here
167#expect_error=main.zig:6:5: error: exported symbol collision: other173#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 {...@@ -8,10 +8,11 @@ pub fn main() !void {
8 try foo(123);8 try foo(123);
9}9}
10fn foo(x: u8) !void {10fn foo(x: u8) !void {
11 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});11 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
12 return stdout_writer.interface.print("{d}\n", .{x});12 return stdout_writer.interface.print("{d}\n", .{x});
13}13}
14const std = @import("std");14const std = @import("std");
15const io = std.Io.Threaded.global_single_threaded.ioBasic();
15#expect_stdout="123\n"16#expect_stdout="123\n"
1617
17#update=change function type18#update=change function type
...@@ -20,10 +21,11 @@ pub fn main() !void {...@@ -20,10 +21,11 @@ pub fn main() !void {
20 try foo(123);21 try foo(123);
21}22}
22fn foo(x: i64) !void {23fn foo(x: i64) !void {
23 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});24 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
24 return stdout_writer.interface.print("{d}\n", .{x});25 return stdout_writer.interface.print("{d}\n", .{x});
25}26}
26const std = @import("std");27const std = @import("std");
28const io = std.Io.Threaded.global_single_threaded.ioBasic();
27#expect_stdout="123\n"29#expect_stdout="123\n"
2830
29#update=change function argument31#update=change function argument
...@@ -32,8 +34,9 @@ pub fn main() !void {...@@ -32,8 +34,9 @@ pub fn main() !void {
32 try foo(-42);34 try foo(-42);
33}35}
34fn foo(x: i64) !void {36fn foo(x: i64) !void {
35 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});37 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
36 return stdout_writer.interface.print("{d}\n", .{x});38 return stdout_writer.interface.print("{d}\n", .{x});
37}39}
38const std = @import("std");40const std = @import("std");
41const io = std.Io.Threaded.global_single_threaded.ioBasic();
39#expect_stdout="-42\n"42#expect_stdout="-42\n"
test/incremental/change_generic_line_number+4-2
...@@ -4,10 +4,11 @@...@@ -4,10 +4,11 @@
4#update=initial version4#update=initial version
5#file=main.zig5#file=main.zig
6const std = @import("std");6const std = @import("std");
7const io = std.Io.Threaded.global_single_threaded.ioBasic();
7fn Printer(message: []const u8) type {8fn Printer(message: []const u8) type {
8 return struct {9 return struct {
9 fn print() !void {10 fn print() !void {
10 try std.fs.File.stdout().writeAll(message);11 try std.Io.File.stdout().writeStreamingAll(io, message);
11 }12 }
12 };13 };
13}14}
...@@ -19,11 +20,12 @@ pub fn main() !void {...@@ -19,11 +20,12 @@ pub fn main() !void {
19#update=change line number20#update=change line number
20#file=main.zig21#file=main.zig
21const std = @import("std");22const std = @import("std");
23const io = std.Io.Threaded.global_single_threaded.ioBasic();
2224
23fn Printer(message: []const u8) type {25fn Printer(message: []const u8) type {
24 return struct {26 return struct {
25 fn print() !void {27 fn print() !void {
26 try std.fs.File.stdout().writeAll(message);28 try std.Io.File.stdout().writeStreamingAll(io, message);
27 }29 }
28 };30 };
29}31}
test/incremental/change_line_number+4-2
...@@ -5,14 +5,16 @@...@@ -5,14 +5,16 @@
5#file=main.zig5#file=main.zig
6const std = @import("std");6const std = @import("std");
7pub fn main() !void {7pub fn main() !void {
8 try std.fs.File.stdout().writeAll("foo\n");8 try std.Io.File.stdout().writeStreamingAll(io, "foo\n");
9}9}
10const io = std.Io.Threaded.global_single_threaded.ioBasic();
10#expect_stdout="foo\n"11#expect_stdout="foo\n"
11#update=change line number12#update=change line number
12#file=main.zig13#file=main.zig
13const std = @import("std");14const std = @import("std");
1415
15pub fn main() !void {16pub fn main() !void {
16 try std.fs.File.stdout().writeAll("foo\n");17 try std.Io.File.stdout().writeStreamingAll(io, "foo\n");
17}18}
19const io = std.Io.Threaded.global_single_threaded.ioBasic();
18#expect_stdout="foo\n"20#expect_stdout="foo\n"
test/incremental/change_panic_handler+6-3
...@@ -12,11 +12,12 @@ pub fn main() !u8 {...@@ -12,11 +12,12 @@ pub fn main() !u8 {
12}12}
13pub const panic = std.debug.FullPanic(myPanic);13pub const panic = std.debug.FullPanic(myPanic);
14fn myPanic(msg: []const u8, _: ?usize) noreturn {14fn 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, &.{});
16 stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {};16 stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {};
17 std.process.exit(0);17 std.process.exit(0);
18}18}
19const std = @import("std");19const std = @import("std");
20const io = std.Io.Threaded.global_single_threaded.ioBasic();
20#expect_stdout="panic message: integer overflow\n"21#expect_stdout="panic message: integer overflow\n"
2122
22#update=change the panic handler body23#update=change the panic handler body
...@@ -29,11 +30,12 @@ pub fn main() !u8 {...@@ -29,11 +30,12 @@ pub fn main() !u8 {
29}30}
30pub const panic = std.debug.FullPanic(myPanic);31pub const panic = std.debug.FullPanic(myPanic);
31fn myPanic(msg: []const u8, _: ?usize) noreturn {32fn 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, &.{});
33 stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {};34 stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {};
34 std.process.exit(0);35 std.process.exit(0);
35}36}
36const std = @import("std");37const std = @import("std");
38const io = std.Io.Threaded.global_single_threaded.ioBasic();
37#expect_stdout="new panic message: integer overflow\n"39#expect_stdout="new panic message: integer overflow\n"
3840
39#update=change the panic handler function value41#update=change the panic handler function value
...@@ -46,9 +48,10 @@ pub fn main() !u8 {...@@ -46,9 +48,10 @@ pub fn main() !u8 {
46}48}
47pub const panic = std.debug.FullPanic(myPanicNew);49pub const panic = std.debug.FullPanic(myPanicNew);
48fn myPanicNew(msg: []const u8, _: ?usize) noreturn {50fn 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, &.{});
50 stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {};52 stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {};
51 std.process.exit(0);53 std.process.exit(0);
52}54}
53const std = @import("std");55const std = @import("std");
56const io = std.Io.Threaded.global_single_threaded.ioBasic();
54#expect_stdout="third panic message: integer overflow\n"57#expect_stdout="third panic message: integer overflow\n"
test/incremental/change_panic_handler_explicit+6-3
...@@ -42,11 +42,12 @@ pub const panic = struct {...@@ -42,11 +42,12 @@ pub const panic = struct {
42 pub const noreturnReturned = no_panic.noreturnReturned;42 pub const noreturnReturned = no_panic.noreturnReturned;
43};43};
44fn myPanic(msg: []const u8, _: ?usize) noreturn {44fn 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, &.{});
46 stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {};46 stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {};
47 std.process.exit(0);47 std.process.exit(0);
48}48}
49const std = @import("std");49const std = @import("std");
50const io = std.Io.Threaded.global_single_threaded.ioBasic();
50#expect_stdout="panic message: integer overflow\n"51#expect_stdout="panic message: integer overflow\n"
5152
52#update=change the panic handler body53#update=change the panic handler body
...@@ -89,11 +90,12 @@ pub const panic = struct {...@@ -89,11 +90,12 @@ pub const panic = struct {
89 pub const noreturnReturned = no_panic.noreturnReturned;90 pub const noreturnReturned = no_panic.noreturnReturned;
90};91};
91fn myPanic(msg: []const u8, _: ?usize) noreturn {92fn 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, &.{});
93 stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {};94 stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {};
94 std.process.exit(0);95 std.process.exit(0);
95}96}
96const std = @import("std");97const std = @import("std");
98const io = std.Io.Threaded.global_single_threaded.ioBasic();
97#expect_stdout="new panic message: integer overflow\n"99#expect_stdout="new panic message: integer overflow\n"
98100
99#update=change the panic handler function value101#update=change the panic handler function value
...@@ -136,9 +138,10 @@ pub const panic = struct {...@@ -136,9 +138,10 @@ pub const panic = struct {
136 pub const noreturnReturned = no_panic.noreturnReturned;138 pub const noreturnReturned = no_panic.noreturnReturned;
137};139};
138fn myPanicNew(msg: []const u8, _: ?usize) noreturn {140fn 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, &.{});
140 stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {};142 stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {};
141 std.process.exit(0);143 std.process.exit(0);
142}144}
143const std = @import("std");145const std = @import("std");
146const io = std.Io.Threaded.global_single_threaded.ioBasic();
144#expect_stdout="third panic message: integer overflow\n"147#expect_stdout="third panic message: integer overflow\n"
test/incremental/change_shift_op+4-2
...@@ -9,10 +9,11 @@ pub fn main() !void {...@@ -9,10 +9,11 @@ pub fn main() !void {
9 try foo(0x1300);9 try foo(0x1300);
10}10}
11fn foo(x: u16) !void {11fn foo(x: u16) !void {
12 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});12 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
13 try stdout_writer.interface.print("0x{x}\n", .{x << 4});13 try stdout_writer.interface.print("0x{x}\n", .{x << 4});
14}14}
15const std = @import("std");15const std = @import("std");
16const io = std.Io.Threaded.global_single_threaded.ioBasic();
16#expect_stdout="0x3000\n"17#expect_stdout="0x3000\n"
17#update=change to right shift18#update=change to right shift
18#file=main.zig19#file=main.zig
...@@ -20,8 +21,9 @@ pub fn main() !void {...@@ -20,8 +21,9 @@ pub fn main() !void {
20 try foo(0x1300);21 try foo(0x1300);
21}22}
22fn foo(x: u16) !void {23fn foo(x: u16) !void {
23 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});24 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
24 try stdout_writer.interface.print("0x{x}\n", .{x >> 4});25 try stdout_writer.interface.print("0x{x}\n", .{x >> 4});
25}26}
26const std = @import("std");27const std = @import("std");
28const io = std.Io.Threaded.global_single_threaded.ioBasic();
27#expect_stdout="0x130\n"29#expect_stdout="0x130\n"
test/incremental/change_struct_same_fields+6-3
...@@ -11,13 +11,14 @@ pub fn main() !void {...@@ -11,13 +11,14 @@ pub fn main() !void {
11 try foo(&val);11 try foo(&val);
12}12}
13fn foo(val: *const S) !void {13fn foo(val: *const S) !void {
14 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});14 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
15 try stdout_writer.interface.print(15 try stdout_writer.interface.print(
16 "{d} {d}\n",16 "{d} {d}\n",
17 .{ val.x, val.y },17 .{ val.x, val.y },
18 );18 );
19}19}
20const std = @import("std");20const std = @import("std");
21const io = std.Io.Threaded.global_single_threaded.ioBasic();
21#expect_stdout="100 200\n"22#expect_stdout="100 200\n"
2223
23#update=change struct layout24#update=change struct layout
...@@ -28,13 +29,14 @@ pub fn main() !void {...@@ -28,13 +29,14 @@ pub fn main() !void {
28 try foo(&val);29 try foo(&val);
29}30}
30fn foo(val: *const S) !void {31fn foo(val: *const S) !void {
31 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});32 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
32 try stdout_writer.interface.print(33 try stdout_writer.interface.print(
33 "{d} {d}\n",34 "{d} {d}\n",
34 .{ val.x, val.y },35 .{ val.x, val.y },
35 );36 );
36}37}
37const std = @import("std");38const std = @import("std");
39const io = std.Io.Threaded.global_single_threaded.ioBasic();
38#expect_stdout="100 200\n"40#expect_stdout="100 200\n"
3941
40#update=change values42#update=change values
...@@ -45,11 +47,12 @@ pub fn main() !void {...@@ -45,11 +47,12 @@ pub fn main() !void {
45 try foo(&val);47 try foo(&val);
46}48}
47fn foo(val: *const S) !void {49fn foo(val: *const S) !void {
48 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});50 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
49 try stdout_writer.interface.print(51 try stdout_writer.interface.print(
50 "{d} {d}\n",52 "{d} {d}\n",
51 .{ val.x, val.y },53 .{ val.x, val.y },
52 );54 );
53}55}
54const std = @import("std");56const std = @import("std");
57const io = std.Io.Threaded.global_single_threaded.ioBasic();
55#expect_stdout="1234 5678\n"58#expect_stdout="1234 5678\n"
test/incremental/change_zon_file+6-3
...@@ -8,8 +8,9 @@...@@ -8,8 +8,9 @@
8const std = @import("std");8const std = @import("std");
9const message: []const u8 = @import("message.zon");9const message: []const u8 = @import("message.zon");
10pub fn main() !void {10pub fn main() !void {
11 try std.fs.File.stdout().writeAll(message);11 try std.Io.File.stdout().writeStreamingAll(io, message);
12}12}
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
13#file=message.zon14#file=message.zon
14"Hello, World!\n"15"Hello, World!\n"
15#expect_stdout="Hello, World!\n"16#expect_stdout="Hello, World!\n"
...@@ -29,8 +30,9 @@ pub fn main() !void {...@@ -29,8 +30,9 @@ pub fn main() !void {
29const std = @import("std");30const std = @import("std");
30const message: []const u8 = @import("message.zon");31const message: []const u8 = @import("message.zon");
31pub fn main() !void {32pub 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");
33}34}
35const io = std.Io.Threaded.global_single_threaded.ioBasic();
34#expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound36#expect_error=message.zon:1:1: error: unable to load 'message.zon': FileNotFound
35#expect_error=main.zig:2:37: note: file imported here37#expect_error=main.zig:2:37: note: file imported here
3638
...@@ -44,6 +46,7 @@ pub fn main() !void {...@@ -44,6 +46,7 @@ pub fn main() !void {
44const std = @import("std");46const std = @import("std");
45const message: []const u8 = @import("message.zon");47const message: []const u8 = @import("message.zon");
46pub fn main() !void {48pub fn main() !void {
47 try std.fs.File.stdout().writeAll(message);49 try std.Io.File.stdout().writeStreamingAll(io, message);
48}50}
51const io = std.Io.Threaded.global_single_threaded.ioBasic();
49#expect_stdout="We're back, World!\n"52#expect_stdout="We're back, World!\n"
test/incremental/change_zon_file_no_result_type+2-1
...@@ -6,8 +6,9 @@...@@ -6,8 +6,9 @@
6#update=initial version6#update=initial version
7#file=main.zig7#file=main.zig
8const std = @import("std");8const std = @import("std");
9const io = std.Io.Threaded.global_single_threaded.ioBasic();
9pub fn main() !void {10pub 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);
11}12}
12#file=foo.zon13#file=foo.zon
13.{14.{
test/incremental/compile_log+6-3
...@@ -8,17 +8,19 @@...@@ -8,17 +8,19 @@
8#file=main.zig8#file=main.zig
9const std = @import("std");9const std = @import("std");
10pub fn main() !void {10pub fn main() !void {
11 try std.fs.File.stdout().writeAll("Hello, World!\n");11 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
12}12}
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
13#expect_stdout="Hello, World!\n"14#expect_stdout="Hello, World!\n"
1415
15#update=add compile log16#update=add compile log
16#file=main.zig17#file=main.zig
17const std = @import("std");18const std = @import("std");
18pub fn main() !void {19pub fn main() !void {
19 try std.fs.File.stdout().writeAll("Hello, World!\n");20 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
20 @compileLog("this is a log");21 @compileLog("this is a log");
21}22}
23const io = std.Io.Threaded.global_single_threaded.ioBasic();
22#expect_error=main.zig:4:5: error: found compile log statement24#expect_error=main.zig:4:5: error: found compile log statement
23#expect_compile_log=@as(*const [13:0]u8, "this is a log")25#expect_compile_log=@as(*const [13:0]u8, "this is a log")
2426
...@@ -26,6 +28,7 @@ pub fn main() !void {...@@ -26,6 +28,7 @@ pub fn main() !void {
26#file=main.zig28#file=main.zig
27const std = @import("std");29const std = @import("std");
28pub fn main() !void {30pub fn main() !void {
29 try std.fs.File.stdout().writeAll("Hello, World!\n");31 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
30}32}
33const io = std.Io.Threaded.global_single_threaded.ioBasic();
31#expect_stdout="Hello, World!\n"34#expect_stdout="Hello, World!\n"
test/incremental/fix_astgen_failure+8-5
...@@ -10,28 +10,31 @@ pub fn main() !void {...@@ -10,28 +10,31 @@ pub fn main() !void {
10}10}
11#file=foo.zig11#file=foo.zig
12pub fn hello() !void {12pub fn hello() !void {
13 try std.fs.File.stdout().writeAll("Hello, World!\n");13 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
14}14}
15#expect_error=foo.zig:2:9: error: use of undeclared identifier 'std'15#expect_error=foo.zig:2:9: error: use of undeclared identifier 'std'
16#update=fix the error16#update=fix the error
17#file=foo.zig17#file=foo.zig
18const std = @import("std");18const std = @import("std");
19pub fn hello() !void {19pub fn hello() !void {
20 try std.fs.File.stdout().writeAll("Hello, World!\n");20 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
21}21}
22const io = std.Io.Threaded.global_single_threaded.ioBasic();
22#expect_stdout="Hello, World!\n"23#expect_stdout="Hello, World!\n"
23#update=add new error24#update=add new error
24#file=foo.zig25#file=foo.zig
25const std = @import("std");26const std = @import("std");
26pub fn hello() !void {27pub fn hello() !void {
27 try std.fs.File.stdout().writeAll(hello_str);28 try std.Io.File.stdout().writeStreamingAll(io, hello_str);
28}29}
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'
30#update=fix the new error32#update=fix the new error
31#file=foo.zig33#file=foo.zig
32const std = @import("std");34const std = @import("std");
33const hello_str = "Hello, World! Again!\n";35const hello_str = "Hello, World! Again!\n";
34pub fn hello() !void {36pub fn hello() !void {
35 try std.fs.File.stdout().writeAll(hello_str);37 try std.Io.File.stdout().writeStreamingAll(io, hello_str);
36}38}
39const io = std.Io.Threaded.global_single_threaded.ioBasic();
37#expect_stdout="Hello, World! Again!\n"40#expect_stdout="Hello, World! Again!\n"
test/incremental/function_becomes_inline+6-3
...@@ -8,9 +8,10 @@ pub fn main() !void {...@@ -8,9 +8,10 @@ pub fn main() !void {
8 try foo();8 try foo();
9}9}
10fn foo() !void {10fn foo() !void {
11 try std.fs.File.stdout().writeAll("Hello, World!\n");11 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
12}12}
13const std = @import("std");13const std = @import("std");
14const io = std.Io.Threaded.global_single_threaded.ioBasic();
14#expect_stdout="Hello, World!\n"15#expect_stdout="Hello, World!\n"
1516
16#update=make function inline17#update=make function inline
...@@ -19,9 +20,10 @@ pub fn main() !void {...@@ -19,9 +20,10 @@ pub fn main() !void {
19 try foo();20 try foo();
20}21}
21inline fn foo() !void {22inline fn foo() !void {
22 try std.fs.File.stdout().writeAll("Hello, World!\n");23 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
23}24}
24const std = @import("std");25const std = @import("std");
26const io = std.Io.Threaded.global_single_threaded.ioBasic();
25#expect_stdout="Hello, World!\n"27#expect_stdout="Hello, World!\n"
2628
27#update=change string29#update=change string
...@@ -30,7 +32,8 @@ pub fn main() !void {...@@ -30,7 +32,8 @@ pub fn main() !void {
30 try foo();32 try foo();
31}33}
32inline fn foo() !void {34inline 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");
34}36}
35const std = @import("std");37const std = @import("std");
38const io = std.Io.Threaded.global_single_threaded.ioBasic();
36#expect_stdout="Hello, `inline` World!\n"39#expect_stdout="Hello, `inline` World!\n"
test/incremental/hello+4-2
...@@ -6,14 +6,16 @@...@@ -6,14 +6,16 @@
6#update=initial version6#update=initial version
7#file=main.zig7#file=main.zig
8const std = @import("std");8const std = @import("std");
9const io = std.Io.Threaded.global_single_threaded.ioBasic();
9pub fn main() !void {10pub fn main() !void {
10 try std.fs.File.stdout().writeAll("good morning\n");11 try std.Io.File.stdout().writeStreamingAll(io, "good morning\n");
11}12}
12#expect_stdout="good morning\n"13#expect_stdout="good morning\n"
13#update=change the string14#update=change the string
14#file=main.zig15#file=main.zig
15const std = @import("std");16const std = @import("std");
17const io = std.Io.Threaded.global_single_threaded.ioBasic();
16pub fn main() !void {18pub fn main() !void {
17 try std.fs.File.stdout().writeAll("おはようございます\n");19 try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n");
18}20}
19#expect_stdout="おはようございます\n"21#expect_stdout="おはようございます\n"
test/incremental/make_decl_pub+4-2
...@@ -12,8 +12,9 @@ pub fn main() !void {...@@ -12,8 +12,9 @@ pub fn main() !void {
12#file=foo.zig12#file=foo.zig
13const std = @import("std");13const std = @import("std");
14fn hello() !void {14fn hello() !void {
15 try std.fs.File.stdout().writeAll("Hello, World!\n");15 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
16}16}
17const io = std.Io.Threaded.global_single_threaded.ioBasic();
17#expect_error=main.zig:3:12: error: 'hello' is not marked 'pub'18#expect_error=main.zig:3:12: error: 'hello' is not marked 'pub'
18#expect_error=foo.zig:2:1: note: declared here19#expect_error=foo.zig:2:1: note: declared here
1920
...@@ -21,6 +22,7 @@ fn hello() !void {...@@ -21,6 +22,7 @@ fn hello() !void {
21#file=foo.zig22#file=foo.zig
22const std = @import("std");23const std = @import("std");
23pub fn hello() !void {24pub fn hello() !void {
24 try std.fs.File.stdout().writeAll("Hello, World!\n");25 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
25}26}
27const io = std.Io.Threaded.global_single_threaded.ioBasic();
26#expect_stdout="Hello, World!\n"28#expect_stdout="Hello, World!\n"
test/incremental/modify_inline_fn+4-2
...@@ -8,20 +8,22 @@...@@ -8,20 +8,22 @@
8const std = @import("std");8const std = @import("std");
9pub fn main() !void {9pub fn main() !void {
10 const str = getStr();10 const str = getStr();
11 try std.fs.File.stdout().writeAll(str);11 try std.Io.File.stdout().writeStreamingAll(io, str);
12}12}
13inline fn getStr() []const u8 {13inline fn getStr() []const u8 {
14 return "foo\n";14 return "foo\n";
15}15}
16const io = std.Io.Threaded.global_single_threaded.ioBasic();
16#expect_stdout="foo\n"17#expect_stdout="foo\n"
17#update=change the string18#update=change the string
18#file=main.zig19#file=main.zig
19const std = @import("std");20const std = @import("std");
20pub fn main() !void {21pub fn main() !void {
21 const str = getStr();22 const str = getStr();
22 try std.fs.File.stdout().writeAll(str);23 try std.Io.File.stdout().writeStreamingAll(io, str);
23}24}
24inline fn getStr() []const u8 {25inline fn getStr() []const u8 {
25 return "bar\n";26 return "bar\n";
26}27}
28const io = std.Io.Threaded.global_single_threaded.ioBasic();
27#expect_stdout="bar\n"29#expect_stdout="bar\n"
test/incremental/move_src+4-2
...@@ -7,7 +7,7 @@...@@ -7,7 +7,7 @@
7#file=main.zig7#file=main.zig
8const std = @import("std");8const std = @import("std");
9pub fn main() !void {9pub fn main() !void {
10 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});10 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
11 try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() });11 try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() });
12}12}
13fn foo() u32 {13fn foo() u32 {
...@@ -16,13 +16,14 @@ fn foo() u32 {...@@ -16,13 +16,14 @@ fn foo() u32 {
16fn bar() u32 {16fn bar() u32 {
17 return 123;17 return 123;
18}18}
19const io = std.Io.Threaded.global_single_threaded.ioBasic();
19#expect_stdout="7 123\n"20#expect_stdout="7 123\n"
2021
21#update=add newline22#update=add newline
22#file=main.zig23#file=main.zig
23const std = @import("std");24const std = @import("std");
24pub fn main() !void {25pub fn main() !void {
25 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});26 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
26 try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() });27 try stdout_writer.interface.print("{d} {d}\n", .{ foo(), bar() });
27}28}
2829
...@@ -32,4 +33,5 @@ fn foo() u32 {...@@ -32,4 +33,5 @@ fn foo() u32 {
32fn bar() u32 {33fn bar() u32 {
33 return 123;34 return 123;
34}35}
36const io = std.Io.Threaded.global_single_threaded.ioBasic();
35#expect_stdout="8 123\n"37#expect_stdout="8 123\n"
test/incremental/no_change_preserves_tag_names+4-2
...@@ -7,15 +7,17 @@...@@ -7,15 +7,17 @@
7#file=main.zig7#file=main.zig
8const std = @import("std");8const std = @import("std");
9var some_enum: enum { first, second } = .first;9var some_enum: enum { first, second } = .first;
10const io = std.Io.Threaded.global_single_threaded.ioBasic();
10pub fn main() !void {11pub fn main() !void {
11 try std.fs.File.stdout().writeAll(@tagName(some_enum));12 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
12}13}
13#expect_stdout="first"14#expect_stdout="first"
14#update=no change15#update=no change
15#file=main.zig16#file=main.zig
16const std = @import("std");17const std = @import("std");
17var some_enum: enum { first, second } = .first;18var some_enum: enum { first, second } = .first;
19const io = std.Io.Threaded.global_single_threaded.ioBasic();
18pub fn main() !void {20pub fn main() !void {
19 try std.fs.File.stdout().writeAll(@tagName(some_enum));21 try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum));
20}22}
21#expect_stdout="first"23#expect_stdout="first"
test/incremental/recursive_function_becomes_non_recursive+7-5
...@@ -9,11 +9,12 @@ pub fn main() !void {...@@ -9,11 +9,12 @@ pub fn main() !void {
9 try foo(false);9 try foo(false);
10}10}
11fn foo(recurse: bool) !void {11fn foo(recurse: bool) !void {
12 const stdout = std.fs.File.stdout();12 const stdout = std.Io.File.stdout();
13 if (recurse) return foo(true);13 if (recurse) return foo(true);
14 try stdout.writeAll("non-recursive path\n");14 try stdout.writeStreamingAll(io, "non-recursive path\n");
15}15}
16const std = @import("std");16const std = @import("std");
17const io = std.Io.Threaded.global_single_threaded.ioBasic();
17#expect_stdout="non-recursive path\n"18#expect_stdout="non-recursive path\n"
1819
19#update=eliminate recursion and change argument20#update=eliminate recursion and change argument
...@@ -22,9 +23,10 @@ pub fn main() !void {...@@ -22,9 +23,10 @@ pub fn main() !void {
22 try foo(true);23 try foo(true);
23}24}
24fn foo(recurse: bool) !void {25fn foo(recurse: bool) !void {
25 const stdout = std.fs.File.stdout();26 const stdout = std.Io.File.stdout();
26 if (recurse) return stdout.writeAll("x==1\n");27 if (recurse) return stdout.writeStreamingAll(io, "x==1\n");
27 try stdout.writeAll("non-recursive path\n");28 try stdout.writeStreamingAll(io, "non-recursive path\n");
28}29}
29const std = @import("std");30const std = @import("std");
31const io = std.Io.Threaded.global_single_threaded.ioBasic();
30#expect_stdout="x==1\n"32#expect_stdout="x==1\n"
test/incremental/remove_enum_field+4-2
...@@ -10,10 +10,11 @@ const MyEnum = enum(u8) {...@@ -10,10 +10,11 @@ const MyEnum = enum(u8) {
10 bar = 2,10 bar = 2,
11};11};
12pub fn main() !void {12pub fn main() !void {
13 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});13 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
14 try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)});14 try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)});
15}15}
16const std = @import("std");16const std = @import("std");
17const io = std.Io.Threaded.global_single_threaded.ioBasic();
17#expect_stdout="1\n"18#expect_stdout="1\n"
18#update=remove enum field19#update=remove enum field
19#file=main.zig20#file=main.zig
...@@ -22,9 +23,10 @@ const MyEnum = enum(u8) {...@@ -22,9 +23,10 @@ const MyEnum = enum(u8) {
22 bar = 2,23 bar = 2,
23};24};
24pub fn main() !void {25pub fn main() !void {
25 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});26 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
26 try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)});27 try stdout_writer.interface.print("{}\n", .{@intFromEnum(MyEnum.foo)});
27}28}
28const std = @import("std");29const std = @import("std");
30const io = std.Io.Threaded.global_single_threaded.ioBasic();
29#expect_error=main.zig:7:69: error: enum 'main.MyEnum' has no member named 'foo'31#expect_error=main.zig:7:69: error: enum 'main.MyEnum' has no member named 'foo'
30#expect_error=main.zig:1:16: note: enum declared here32#expect_error=main.zig:1:16: note: enum declared here
test/incremental/unreferenced_error+8-4
...@@ -7,36 +7,40 @@...@@ -7,36 +7,40 @@
7#file=main.zig7#file=main.zig
8const std = @import("std");8const std = @import("std");
9pub fn main() !void {9pub fn main() !void {
10 try std.fs.File.stdout().writeAll(a);10 try std.Io.File.stdout().writeStreamingAll(io, a);
11}11}
12const a = "Hello, World!\n";12const a = "Hello, World!\n";
13const io = std.Io.Threaded.global_single_threaded.ioBasic();
13#expect_stdout="Hello, World!\n"14#expect_stdout="Hello, World!\n"
1415
15#update=introduce compile error16#update=introduce compile error
16#file=main.zig17#file=main.zig
17const std = @import("std");18const std = @import("std");
18pub fn main() !void {19pub fn main() !void {
19 try std.fs.File.stdout().writeAll(a);20 try std.Io.File.stdout().writeStreamingAll(io, a);
20}21}
21const a = @compileError("bad a");22const a = @compileError("bad a");
23const io = std.Io.Threaded.global_single_threaded.ioBasic();
22#expect_error=main.zig:5:11: error: bad a24#expect_error=main.zig:5:11: error: bad a
2325
24#update=remove error reference26#update=remove error reference
25#file=main.zig27#file=main.zig
26const std = @import("std");28const std = @import("std");
27pub fn main() !void {29pub fn main() !void {
28 try std.fs.File.stdout().writeAll(b);30 try std.Io.File.stdout().writeStreamingAll(io, b);
29}31}
30const a = @compileError("bad a");32const a = @compileError("bad a");
31const b = "Hi there!\n";33const b = "Hi there!\n";
34const io = std.Io.Threaded.global_single_threaded.ioBasic();
32#expect_stdout="Hi there!\n"35#expect_stdout="Hi there!\n"
3336
34#update=introduce and remove reference to error37#update=introduce and remove reference to error
35#file=main.zig38#file=main.zig
36const std = @import("std");39const std = @import("std");
37pub fn main() !void {40pub fn main() !void {
38 try std.fs.File.stdout().writeAll(a);41 try std.Io.File.stdout().writeStreamingAll(io, a);
39}42}
40const a = "Back to a\n";43const a = "Back to a\n";
41const b = @compileError("bad b");44const b = @compileError("bad b");
45const io = std.Io.Threaded.global_single_threaded.ioBasic();
42#expect_stdout="Back to a\n"46#expect_stdout="Back to a\n"
test/link/bss/main.zig+1-1
...@@ -4,7 +4,7 @@ const std = @import("std");...@@ -4,7 +4,7 @@ const std = @import("std");
4var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000;4var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000;
55
6pub fn main() anyerror!void {6pub 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
9 buffer[0x10] = 1;9 buffer[0x10] = 1;
1010
test/link/elf.zig+2-2
...@@ -1323,7 +1323,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step {...@@ -1323,7 +1323,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step {
1323 \\extern var live_var2: i32;1323 \\extern var live_var2: i32;
1324 \\extern fn live_fn2() void;1324 \\extern fn live_fn2() void;
1325 \\pub fn main() void {1325 \\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, &.{});
1327 \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail");1327 \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail");
1328 \\ live_fn2();1328 \\ live_fn2();
1329 \\}1329 \\}
...@@ -1365,7 +1365,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step {...@@ -1365,7 +1365,7 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step {
1365 \\extern var live_var2: i32;1365 \\extern var live_var2: i32;
1366 \\extern fn live_fn2() void;1366 \\extern fn live_fn2() void;
1367 \\pub fn main() void {1367 \\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, &.{});
1369 \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail");1369 \\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail");
1370 \\ live_fn2();1370 \\ live_fn2();
1371 \\}1371 \\}
test/link/macho.zig+4-3
...@@ -716,7 +716,7 @@ fn testHelloZig(b: *Build, opts: Options) *Step {...@@ -716,7 +716,7 @@ fn testHelloZig(b: *Build, opts: Options) *Step {
716 const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =716 const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
717 \\const std = @import("std");717 \\const std = @import("std");
718 \\pub fn main() void {718 \\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");
720 \\}720 \\}
721 });721 });
722722
...@@ -868,9 +868,10 @@ fn testLayout(b: *Build, opts: Options) *Step {...@@ -868,9 +868,10 @@ fn testLayout(b: *Build, opts: Options) *Step {
868}868}
869869
870fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step {870fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step {
871 const io = b.graph.io;
871 const test_step = addTestStep(b, "link-directly-cpp-tbd", opts);872 const test_step = addTestStep(b, "link-directly-cpp-tbd", opts);
872873
873 const sdk = std.zig.system.darwin.getSdk(b.allocator, &opts.target.result) orelse874 const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse
874 @panic("macOS SDK is required to run the test");875 @panic("macOS SDK is required to run the test");
875876
876 const exe = addExecutable(b, opts, .{877 const exe = addExecutable(b, opts, .{
...@@ -2371,7 +2372,7 @@ fn testTlsZig(b: *Build, opts: Options) *Step {...@@ -2371,7 +2372,7 @@ fn testTlsZig(b: *Build, opts: Options) *Step {
2371 \\threadlocal var x: i32 = 0;2372 \\threadlocal var x: i32 = 0;
2372 \\threadlocal var y: i32 = -1;2373 \\threadlocal var y: i32 = -1;
2373 \\pub fn main() void {2374 \\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, &.{});
2375 \\ stdout_writer.interface.print("{d} {d}\n", .{x, y}) catch unreachable;2376 \\ stdout_writer.interface.print("{d} {d}\n", .{x, y}) catch unreachable;
2376 \\ x -= 1;2377 \\ x -= 1;
2377 \\ y += 1;2378 \\ y += 1;
test/link/wasm/extern/main.zig+1-1
...@@ -3,6 +3,6 @@ const std = @import("std");...@@ -3,6 +3,6 @@ const std = @import("std");
3extern const foo: u32;3extern const foo: u32;
44
5pub fn main() void {5pub 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, &.{});
7 stdout_writer.interface.print("Result: {d}", .{foo}) catch {};7 stdout_writer.interface.print("Result: {d}", .{foo}) catch {};
8}8}
test/src/Cases.zig+18-6
...@@ -1,6 +1,8 @@...@@ -1,6 +1,8 @@
1const Cases = @This();1const Cases = @This();
2const builtin = @import("builtin");2const builtin = @import("builtin");
3
3const std = @import("std");4const std = @import("std");
5const Io = std.Io;
4const assert = std.debug.assert;6const assert = std.debug.assert;
5const Allocator = std.mem.Allocator;7const Allocator = std.mem.Allocator;
6const getExternalExecutor = std.zig.system.getExternalExecutor;8const getExternalExecutor = std.zig.system.getExternalExecutor;
...@@ -8,6 +10,7 @@ const ArrayList = std.ArrayList;...@@ -8,6 +10,7 @@ const ArrayList = std.ArrayList;
810
9gpa: Allocator,11gpa: Allocator,
10arena: Allocator,12arena: Allocator,
13io: Io,
11cases: std.array_list.Managed(Case),14cases: std.array_list.Managed(Case),
1215
13pub const IncrementalCase = struct {16pub const IncrementalCase = struct {
...@@ -313,7 +316,7 @@ pub fn addCompile(...@@ -313,7 +316,7 @@ pub fn addCompile(
313/// Each file should include a test manifest as a contiguous block of comments at316/// Each file should include a test manifest as a contiguous block of comments at
314/// the end of the file. The first line should be the test type, followed by a set of317/// the end of the file. The first line should be the test type, followed by a set of
315/// key-value config values, followed by a blank line, then the expected output.318/// 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 {
317 var current_file: []const u8 = "none";320 var current_file: []const u8 = "none";
318 ctx.addFromDirInner(dir, &current_file, b) catch |err| {321 ctx.addFromDirInner(dir, &current_file, b) catch |err| {
319 std.debug.panicExtra(322 std.debug.panicExtra(
...@@ -326,16 +329,17 @@ pub fn addFromDir(ctx: *Cases, dir: std.fs.Dir, b: *std.Build) void {...@@ -326,16 +329,17 @@ pub fn addFromDir(ctx: *Cases, dir: std.fs.Dir, b: *std.Build) void {
326329
327fn addFromDirInner(330fn addFromDirInner(
328 ctx: *Cases,331 ctx: *Cases,
329 iterable_dir: std.fs.Dir,332 iterable_dir: Io.Dir,
330 /// This is kept up to date with the currently being processed file so333 /// This is kept up to date with the currently being processed file so
331 /// that if any errors occur the caller knows it happened during this file.334 /// that if any errors occur the caller knows it happened during this file.
332 current_file: *[]const u8,335 current_file: *[]const u8,
333 b: *std.Build,336 b: *std.Build,
334) !void {337) !void {
338 const io = ctx.io;
335 var it = try iterable_dir.walk(ctx.arena);339 var it = try iterable_dir.walk(ctx.arena);
336 var filenames: ArrayList([]const u8) = .empty;340 var filenames: ArrayList([]const u8) = .empty;
337341
338 while (try it.next()) |entry| {342 while (try it.next(io)) |entry| {
339 if (entry.kind != .file) continue;343 if (entry.kind != .file) continue;
340344
341 // Ignore stuff such as .swp files345 // Ignore stuff such as .swp files
...@@ -347,7 +351,7 @@ fn addFromDirInner(...@@ -347,7 +351,7 @@ fn addFromDirInner(
347 current_file.* = filename;351 current_file.* = filename;
348352
349 const max_file_size = 10 * 1024 * 1024;353 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
352 // Parse the manifest356 // Parse the manifest
353 var manifest = try TestManifest.parse(ctx.arena, src);357 var manifest = try TestManifest.parse(ctx.arena, src);
...@@ -376,6 +380,12 @@ fn addFromDirInner(...@@ -376,6 +380,12 @@ fn addFromDirInner(
376 // Other backends don't support new liveness format380 // Other backends don't support new liveness format
377 continue;381 continue;
378 }382 }
383
384 if (backend == .selfhosted and target.cpu.arch == .aarch64) {
385 // https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9182045
386 continue;
387 }
388
379 if (backend == .selfhosted and target.os.tag == .macos and389 if (backend == .selfhosted and target.os.tag == .macos and
380 target.cpu.arch == .x86_64 and builtin.cpu.arch == .aarch64)390 target.cpu.arch == .x86_64 and builtin.cpu.arch == .aarch64)
381 {391 {
...@@ -427,9 +437,10 @@ fn addFromDirInner(...@@ -427,9 +437,10 @@ fn addFromDirInner(
427 }437 }
428}438}
429439
430pub fn init(gpa: Allocator, arena: Allocator) Cases {440pub fn init(gpa: Allocator, arena: Allocator, io: Io) Cases {
431 return .{441 return .{
432 .gpa = gpa,442 .gpa = gpa,
443 .io = io,
433 .cases = .init(gpa),444 .cases = .init(gpa),
434 .arena = arena,445 .arena = arena,
435 };446 };
...@@ -457,6 +468,7 @@ pub fn lowerToBuildSteps(...@@ -457,6 +468,7 @@ pub fn lowerToBuildSteps(
457 parent_step: *std.Build.Step,468 parent_step: *std.Build.Step,
458 options: CaseTestOptions,469 options: CaseTestOptions,
459) void {470) void {
471 const io = self.io;
460 const host = b.resolveTargetQuery(.{});472 const host = b.resolveTargetQuery(.{});
461 const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM");473 const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM");
462474
...@@ -591,7 +603,7 @@ pub fn lowerToBuildSteps(...@@ -591,7 +603,7 @@ pub fn lowerToBuildSteps(
591 },603 },
592 .Execution => |expected_stdout| no_exec: {604 .Execution => |expected_stdout| no_exec: {
593 const run = if (case.target.result.ofmt == .c) run_step: {605 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) {
595 // We wouldn't be able to run the compiled C code.607 // We wouldn't be able to run the compiled C code.
596 break :no_exec;608 break :no_exec;
597 }609 }
test/src/convert-stack-trace.zig+5-5
...@@ -34,20 +34,20 @@ pub fn main() !void {...@@ -34,20 +34,20 @@ pub fn main() !void {
3434
35 const gpa = arena;35 const gpa = arena;
3636
37 var threaded: std.Io.Threaded = .init(gpa);37 var threaded: std.Io.Threaded = .init(gpa, .{});
38 defer threaded.deinit();38 defer threaded.deinit();
39 const io = threaded.io();39 const io = threaded.io();
4040
41 var read_buf: [1024]u8 = undefined;41 var read_buf: [1024]u8 = undefined;
42 var write_buf: [1024]u8 = undefined;42 var write_buf: [1024]u8 = undefined;
4343
44 const in_file = try std.fs.cwd().openFile(args[1], .{});44 const in_file = try std.Io.Dir.cwd().openFile(io, args[1], .{});
45 defer in_file.close();45 defer in_file.close(io);
4646
47 const out_file: std.fs.File = .stdout();47 const out_file: std.Io.File = .stdout();
4848
49 var in_fr = in_file.reader(io, &read_buf);49 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
52 const w = &out_fw.interface;52 const w = &out_fw.interface;
5353
test/standalone/child_process/child.zig+8-8
...@@ -8,7 +8,7 @@ pub fn main() !void {...@@ -8,7 +8,7 @@ pub fn main() !void {
8 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);8 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
9 const arena = arena_state.allocator();9 const arena = arena_state.allocator();
1010
11 var threaded: std.Io.Threaded = .init(arena);11 var threaded: std.Io.Threaded = .init(arena, .{});
12 defer threaded.deinit();12 defer threaded.deinit();
13 const io = threaded.io();13 const io = threaded.io();
1414
...@@ -26,28 +26,28 @@ fn run(allocator: std.mem.Allocator, io: Io) !void {...@@ -26,28 +26,28 @@ fn run(allocator: std.mem.Allocator, io: Io) !void {
26 const hello_arg = "hello arg";26 const hello_arg = "hello arg";
27 const a1 = args.next() orelse unreachable;27 const a1 = args.next() orelse unreachable;
28 if (!std.mem.eql(u8, a1, hello_arg)) {28 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 });
30 }30 }
31 if (args.next()) |a2| {31 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});
33 }33 }
3434
35 // test stdout pipe; parent verifies35 // 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
38 // test stdin pipe from parent38 // test stdin pipe from parent
39 const hello_stdin = "hello from stdin";39 const hello_stdin = "hello from stdin";
40 var buf: [hello_stdin.len]u8 = undefined;40 var buf: [hello_stdin.len]u8 = undefined;
41 const stdin: std.fs.File = .stdin();41 const stdin: std.Io.File = .stdin();
42 var reader = stdin.reader(io, &.{});42 var reader = stdin.reader(io, &.{});
43 const n = try reader.interface.readSliceShort(&buf);43 const n = try reader.interface.readSliceShort(&buf);
44 if (!std.mem.eql(u8, buf[0..n], hello_stdin)) {44 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 });
46 }46 }
47}47}
4848
49fn testError(comptime fmt: []const u8, args: anytype) void {49fn testError(io: Io, comptime fmt: []const u8, args: anytype) void {
50 var stderr_writer = std.fs.File.stderr().writer(&.{});50 var stderr_writer = std.Io.File.stderr().writer(io, &.{});
51 const stderr = &stderr_writer.interface;51 const stderr = &stderr_writer.interface;
52 stderr.print("CHILD TEST ERROR: ", .{}) catch {};52 stderr.print("CHILD TEST ERROR: ", .{}) catch {};
53 stderr.print(fmt, args) catch {};53 stderr.print(fmt, args) catch {};
test/standalone/child_process/main.zig+12-11
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
23
3pub fn main() !void {4pub fn main() !void {
4 // make sure safety checks are enabled even in release modes5 // make sure safety checks are enabled even in release modes
...@@ -20,7 +21,7 @@ pub fn main() !void {...@@ -20,7 +21,7 @@ pub fn main() !void {
20 };21 };
21 defer if (needs_free) gpa.free(child_path);22 defer if (needs_free) gpa.free(child_path);
2223
23 var threaded: std.Io.Threaded = .init(gpa);24 var threaded: Io.Threaded = .init(gpa, .{});
24 defer threaded.deinit();25 defer threaded.deinit();
25 const io = threaded.io();26 const io = threaded.io();
2627
...@@ -28,10 +29,10 @@ pub fn main() !void {...@@ -28,10 +29,10 @@ pub fn main() !void {
28 child.stdin_behavior = .Pipe;29 child.stdin_behavior = .Pipe;
29 child.stdout_behavior = .Pipe;30 child.stdout_behavior = .Pipe;
30 child.stderr_behavior = .Inherit;31 child.stderr_behavior = .Inherit;
31 try child.spawn();32 try child.spawn(io);
32 const child_stdin = child.stdin.?;33 const child_stdin = child.stdin.?;
33 try child_stdin.writeAll("hello from stdin"); // verified in child34 try child_stdin.writeStreamingAll(io, "hello from stdin"); // verified in child
34 child_stdin.close();35 child_stdin.close(io);
35 child.stdin = null;36 child.stdin = null;
3637
37 const hello_stdout = "hello from stdout";38 const hello_stdout = "hello from stdout";
...@@ -39,30 +40,30 @@ pub fn main() !void {...@@ -39,30 +40,30 @@ pub fn main() !void {
39 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});40 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
40 const n = try stdout_reader.interface.readSliceShort(&buf);41 const n = try stdout_reader.interface.readSliceShort(&buf);
41 if (!std.mem.eql(u8, buf[0..n], hello_stdout)) {42 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 });
43 }44 }
4445
45 switch (try child.wait()) {46 switch (try child.wait(io)) {
46 .Exited => |code| {47 .Exited => |code| {
47 const child_ok_code = 42; // set by child if no test errors48 const child_ok_code = 42; // set by child if no test errors
48 if (code != child_ok_code) {49 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 });
50 }51 }
51 },52 },
52 else => |term| testError("abnormal child exit: {}", .{term}),53 else => |term| testError(io, "abnormal child exit: {}", .{term}),
53 }54 }
54 if (parent_test_error) return error.ParentTestError;55 if (parent_test_error) return error.ParentTestError;
5556
56 // Check that FileNotFound is consistent across platforms when trying to spawn an executable that doesn't exist57 // Check that FileNotFound is consistent across platforms when trying to spawn an executable that doesn't exist
57 const missing_child_path = try std.mem.concat(gpa, u8, &.{ child_path, "_intentionally_missing" });58 const missing_child_path = try std.mem.concat(gpa, u8, &.{ child_path, "_intentionally_missing" });
58 defer gpa.free(missing_child_path);59 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} }));
60}61}
6162
62var parent_test_error = false;63var parent_test_error = false;
6364
64fn testError(comptime fmt: []const u8, args: anytype) void {65fn testError(io: Io, comptime fmt: []const u8, args: anytype) void {
65 var stderr_writer = std.fs.File.stderr().writer(&.{});66 var stderr_writer = Io.File.stderr().writer(io, &.{});
66 const stderr = &stderr_writer.interface;67 const stderr = &stderr_writer.interface;
67 stderr.print("PARENT TEST ERROR: ", .{}) catch {};68 stderr.print("PARENT TEST ERROR: ", .{}) catch {};
68 stderr.print(fmt, args) catch {};69 stderr.print(fmt, args) catch {};
test/standalone/cmakedefine/check.zig+4-2
...@@ -9,8 +9,10 @@ pub fn main() !void {...@@ -9,8 +9,10 @@ pub fn main() !void {
9 const actual_path = args[1];9 const actual_path = args[1];
10 const expected_path = args[2];10 const expected_path = args[2];
1111
12 const actual = try std.fs.cwd().readFileAlloc(actual_path, arena, .limited(1024 * 1024));12 const io = std.Io.Threaded.global_single_threaded.ioBasic();
13 const expected = try std.fs.cwd().readFileAlloc(expected_path, arena, .limited(1024 * 1024));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
15 // The actual output starts with a comment which we should strip out before comparing.17 // The actual output starts with a comment which we should strip out before comparing.
16 const comment_str = "/* This file was generated by ConfigHeader using the Zig Build System. */\n";18 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 {...@@ -11,7 +11,7 @@ pub fn main() void {
11 var di: std.debug.SelfInfo = .init;11 var di: std.debug.SelfInfo = .init;
12 defer di.deinit(gpa);12 defer di.deinit(gpa);
1313
14 var threaded: std.Io.Threaded = .init(gpa);14 var threaded: std.Io.Threaded = .init(gpa, .{});
15 defer threaded.deinit();15 defer threaded.deinit();
16 const io = threaded.io();16 const io = threaded.io();
1717
test/standalone/dirname/build.zig+6-4
...@@ -59,13 +59,15 @@ pub fn build(b: *std.Build) void {...@@ -59,13 +59,15 @@ pub fn build(b: *std.Build) void {
5959
60 // Absolute path:60 // Absolute path:
61 const abs_path = setup_abspath: {61 const abs_path = setup_abspath: {
62 // TODO this is a bad pattern, don't do this
63 const io = b.graph.io;
62 const temp_dir = b.makeTempPath();64 const temp_dir = b.makeTempPath();
6365
64 var dir = std.fs.cwd().openDir(temp_dir, .{}) catch @panic("failed to open temp dir");66 var dir = std.Io.Dir.cwd().openDir(io, temp_dir, .{}) catch @panic("failed to open temp dir");
65 defer dir.close();67 defer dir.close(io);
6668
67 var file = dir.createFile("foo.txt", .{}) catch @panic("failed to create file");69 var file = dir.createFile(io, "foo.txt", .{}) catch @panic("failed to create file");
68 file.close();70 file.close(io);
6971
70 break :setup_abspath std.Build.LazyPath{ .cwd_relative = temp_dir };72 break :setup_abspath std.Build.LazyPath{ .cwd_relative = temp_dir };
71 };73 };
test/standalone/dirname/exists_in.zig+5-3
...@@ -34,8 +34,10 @@ fn run(allocator: std.mem.Allocator) !void {...@@ -34,8 +34,10 @@ fn run(allocator: std.mem.Allocator) !void {
34 return error.BadUsage;34 return error.BadUsage;
35 };35 };
3636
37 var dir = try std.fs.cwd().openDir(dir_path, .{});37 const io = std.Io.Threaded.global_single_threaded.ioBasic();
38 defer dir.close();
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, .{});
41}43}
test/standalone/dirname/touch.zig+9-7
...@@ -26,14 +26,16 @@ fn run(allocator: std.mem.Allocator) !void {...@@ -26,14 +26,16 @@ fn run(allocator: std.mem.Allocator) !void {
26 return error.BadUsage;26 return error.BadUsage;
27 };27 };
2828
29 const dir_path = std.fs.path.dirname(path) orelse unreachable;29 const dir_path = std.Io.Dir.path.dirname(path) orelse unreachable;
30 const basename = std.fs.path.basename(path);30 const basename = std.Io.Dir.path.basename(path);
3131
32 var dir = try std.fs.cwd().openDir(dir_path, .{});32 const io = std.Io.Threaded.global_single_threaded.ioBasic();
33 defer dir.close();
3433
35 _ = dir.statFile(basename) catch {34 var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{});
36 var file = try dir.createFile(basename, .{});35 defer dir.close(io);
37 file.close();36
37 _ = dir.statFile(io, basename, .{}) catch {
38 var file = try dir.createFile(io, basename, .{});
39 file.close(io);
38 };40 };
39}41}
test/standalone/entry_point/check_differ.zig+4-2
...@@ -6,8 +6,10 @@ pub fn main() !void {...@@ -6,8 +6,10 @@ pub fn main() !void {
6 const args = try std.process.argsAlloc(arena);6 const args = try std.process.argsAlloc(arena);
7 if (args.len != 3) return error.BadUsage; // usage: 'check_differ <path a> <path b>'7 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 plenty9 const io = std.Io.Threaded.global_single_threaded.ioBasic();
10 const contents_2 = try std.fs.cwd().readFileAlloc(args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty10
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
12 if (std.mem.eql(u8, contents_1, contents_2)) {14 if (std.mem.eql(u8, contents_1, contents_2)) {
13 return error.FilesMatch;15 return error.FilesMatch;
test/standalone/env_vars/main.zig+2
...@@ -3,6 +3,8 @@ const builtin = @import("builtin");...@@ -3,6 +3,8 @@ const builtin = @import("builtin");
33
4// Note: the environment variables under test are set by the build.zig4// Note: the environment variables under test are set by the build.zig
5pub fn main() !void {5pub fn main() !void {
6 @setEvalBranchQuota(10000);
7
6 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;8 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
7 defer _ = gpa.deinit();9 defer _ = gpa.deinit();
8 const allocator = gpa.allocator();10 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;...@@ -28,10 +28,10 @@ extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: [*]const u8) c_int;
2828
29// PR #17034 - fstat moved between libc_nonshared and libc29// PR #17034 - fstat moved between libc_nonshared and libc
30fn checkStat() !void {30fn checkStat() !void {
31 const cwdFd = std.fs.cwd().fd;31 const cwd_fd = std.Io.Dir.cwd().handle;
3232
33 var buf: [256]u8 = @splat(0);33 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);
35 assert(result == -1);35 assert(result == -1);
36 assert(std.posix.errno(result) == .NOENT);36 assert(std.posix.errno(result) == .NOENT);
3737
test/standalone/install_headers/check_exists.zig+6-4
...@@ -11,8 +11,10 @@ pub fn main() !void {...@@ -11,8 +11,10 @@ pub fn main() !void {
11 var arg_it = try std.process.argsWithAllocator(arena);11 var arg_it = try std.process.argsWithAllocator(arena);
12 _ = arg_it.next();12 _ = arg_it.next();
1313
14 const cwd = std.fs.cwd();14 const io = std.Io.Threaded.global_single_threaded.ioBasic();
15 const cwd_realpath = try cwd.realpathAlloc(arena, ".");15
16 const cwd = std.Io.Dir.cwd();
17 const cwd_realpath = try cwd.realPathFileAlloc(io, ".", arena);
1618
17 while (arg_it.next()) |file_path| {19 while (arg_it.next()) |file_path| {
18 if (file_path.len > 0 and file_path[0] == '!') {20 if (file_path.len > 0 and file_path[0] == '!') {
...@@ -20,7 +22,7 @@ pub fn main() !void {...@@ -20,7 +22,7 @@ pub fn main() !void {
20 "exclusive file check '{s}{c}{s}' failed",22 "exclusive file check '{s}{c}{s}' failed",
21 .{ cwd_realpath, std.fs.path.sep, file_path[1..] },23 .{ cwd_realpath, std.fs.path.sep, file_path[1..] },
22 );24 );
23 if (std.fs.cwd().statFile(file_path[1..])) |_| {25 if (cwd.statFile(io, file_path[1..], .{})) |_| {
24 return error.FileFound;26 return error.FileFound;
25 } else |err| switch (err) {27 } else |err| switch (err) {
26 error.FileNotFound => {},28 error.FileNotFound => {},
...@@ -31,7 +33,7 @@ pub fn main() !void {...@@ -31,7 +33,7 @@ pub fn main() !void {
31 "inclusive file check '{s}{c}{s}' failed",33 "inclusive file check '{s}{c}{s}' failed",
32 .{ cwd_realpath, std.fs.path.sep, file_path },34 .{ cwd_realpath, std.fs.path.sep, file_path },
33 );35 );
34 _ = try std.fs.cwd().statFile(file_path);36 _ = try cwd.statFile(io, file_path, .{});
35 }37 }
36 }38 }
37}39}
test/standalone/ios/build.zig+3-1
...@@ -23,7 +23,9 @@ pub fn build(b: *std.Build) void {...@@ -23,7 +23,9 @@ pub fn build(b: *std.Build) void {
23 }),23 }),
24 });24 });
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| {
27 b.sysroot = sdk;29 b.sysroot = sdk;
28 exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) });30 exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) });
29 exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) });31 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 {...@@ -15,13 +15,13 @@ pub fn main() !void {
15 defer args.deinit();15 defer args.deinit();
16 _ = args.skip(); // executable name16 _ = args.skip(); // executable name
1717
18 var threaded: std.Io.Threaded = .init(gpa);18 var threaded: std.Io.Threaded = .init(gpa, .{});
19 defer threaded.deinit();19 defer threaded.deinit();
20 const io = threaded.io();20 const io = threaded.io();
2121
22 const cache_dir_path = args.next() orelse @panic("expected cache directory path argument");22 const cache_dir_path = args.next() orelse @panic("expected cache directory path argument");
23 var cache_dir = try std.fs.cwd().openDir(cache_dir_path, .{});23 var cache_dir = try std.Io.Dir.cwd().openDir(io, cache_dir_path, .{});
24 defer cache_dir.close();24 defer cache_dir.close(io);
2525
26 abi.fuzzer_init(.fromSlice(cache_dir_path));26 abi.fuzzer_init(.fromSlice(cache_dir_path));
27 abi.fuzzer_init_test(testOne, .fromSlice("test"));27 abi.fuzzer_init_test(testOne, .fromSlice("test"));
...@@ -30,8 +30,8 @@ pub fn main() !void {...@@ -30,8 +30,8 @@ pub fn main() !void {
3030
31 const pc_digest = abi.fuzzer_coverage().id;31 const pc_digest = abi.fuzzer_coverage().id;
32 const coverage_file_path = "v/" ++ std.fmt.hex(pc_digest);32 const coverage_file_path = "v/" ++ std.fmt.hex(pc_digest);
33 const coverage_file = try cache_dir.openFile(coverage_file_path, .{});33 const coverage_file = try cache_dir.openFile(io, coverage_file_path, .{});
34 defer coverage_file.close();34 defer coverage_file.close(io);
3535
36 var read_buf: [@sizeOf(abi.SeenPcsHeader)]u8 = undefined;36 var read_buf: [@sizeOf(abi.SeenPcsHeader)]u8 = undefined;
37 var r = coverage_file.reader(io, &read_buf);37 var r = coverage_file.reader(io, &read_buf);
...@@ -42,6 +42,6 @@ pub fn main() !void {...@@ -42,6 +42,6 @@ pub fn main() !void {
42 const expected_len = @sizeOf(abi.SeenPcsHeader) +42 const expected_len = @sizeOf(abi.SeenPcsHeader) +
43 try std.math.divCeil(usize, pcs_header.pcs_len, @bitSizeOf(usize)) * @sizeOf(usize) +43 try std.math.divCeil(usize, pcs_header.pcs_len, @bitSizeOf(usize)) * @sizeOf(usize) +
44 pcs_header.pcs_len * @sizeOf(usize);44 pcs_header.pcs_len * @sizeOf(usize);
45 if (try coverage_file.getEndPos() != expected_len)45 if (try coverage_file.length(io) != expected_len)
46 return error.WrongEnd;46 return error.WrongEnd;
47}47}
test/standalone/posix/cwd.zig+63-16
...@@ -1,21 +1,30 @@...@@ -1,21 +1,30 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
32
3const std = @import("std");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;
6const assert = std.debug.assert;
7
4const path_max = std.fs.max_path_bytes;8const path_max = std.fs.max_path_bytes;
59
6pub fn main() !void {10pub fn main() !void {
7 if (builtin.target.os.tag == .wasi) {11 switch (builtin.target.os.tag) {
8 // WASI doesn't support changing the working directory at all.12 .wasi => return, // WASI doesn't support changing the working directory at all.
9 return;13 .windows => return, // POSIX is not implemented by Windows
14 else => {},
10 }15 }
1116
12 var Allocator = std.heap.DebugAllocator(.{}){};17 var debug_allocator: std.heap.DebugAllocator(.{}) = .{};
13 const a = Allocator.allocator();18 defer assert(debug_allocator.deinit() == .ok);
14 defer std.debug.assert(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
16 try test_chdir_self();25 try test_chdir_self();
17 try test_chdir_absolute();26 try test_chdir_absolute();
18 try test_chdir_relative(a);27 try test_chdir_relative(gpa, io);
19}28}
2029
21// get current working directory and expect it to match given path30// get current working directory and expect it to match given path
...@@ -46,20 +55,20 @@ fn test_chdir_absolute() !void {...@@ -46,20 +55,20 @@ fn test_chdir_absolute() !void {
46 try expect_cwd(parent);55 try expect_cwd(parent);
47}56}
4857
49fn test_chdir_relative(a: std.mem.Allocator) !void {58fn test_chdir_relative(gpa: Allocator, io: Io) !void {
50 var tmp = std.testing.tmpDir(.{});59 var tmp = tmpDir(io, .{});
51 defer tmp.cleanup();60 defer tmp.cleanup(io);
5261
53 // Use the tmpDir parent_dir as the "base" for the test. Then cd into the child62 // 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
56 // Capture base working directory path, to build expected full path65 // Capture base working directory path, to build expected full path
57 var base_cwd_buf: [path_max]u8 = undefined;66 var base_cwd_buf: [path_max]u8 = undefined;
58 const base_cwd = try std.posix.getcwd(base_cwd_buf[0..]);67 const base_cwd = try std.posix.getcwd(base_cwd_buf[0..]);
5968
60 const relative_dir_name = &tmp.sub_path;69 const relative_dir_name = &tmp.sub_path;
61 const expected_path = try std.fs.path.resolve(a, &.{ base_cwd, relative_dir_name });70 const expected_path = try std.fs.path.resolve(gpa, &.{ base_cwd, relative_dir_name });
62 defer a.free(expected_path);71 defer gpa.free(expected_path);
6372
64 // change current working directory to new test directory73 // change current working directory to new test directory
65 try std.posix.chdir(relative_dir_name);74 try std.posix.chdir(relative_dir_name);
...@@ -68,8 +77,46 @@ fn test_chdir_relative(a: std.mem.Allocator) !void {...@@ -68,8 +77,46 @@ fn test_chdir_relative(a: std.mem.Allocator) !void {
68 const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]);77 const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]);
6978
70 // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase79 // 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});80 const resolved_cwd = try std.fs.path.resolve(gpa, &.{new_cwd});
72 defer a.free(resolved_cwd);81 defer gpa.free(resolved_cwd);
7382
74 try std.testing.expectEqualStrings(expected_path, resolved_cwd);83 try std.testing.expectEqualStrings(expected_path, resolved_cwd);
75}84}
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 @@...@@ -1,71 +1,32 @@
1// Test relative paths through POSIX APIS. These tests have to change the cwd, so1// Test relative paths through POSIX APIS. These tests have to change the cwd, so
2// they shouldn't be Zig unit tests.2// they shouldn't be Zig unit tests.
33
4const std = @import("std");
5const builtin = @import("builtin");4const builtin = @import("builtin");
65
6const std = @import("std");
7const Io = std.Io;
8
7pub fn main() !void {9pub fn main() !void {
8 if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir10 if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir
911
10 var Allocator = std.heap.DebugAllocator(.{}){};12 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
11 const a = Allocator.allocator();13 const gpa = debug_allocator.allocator();
12 defer std.debug.assert(Allocator.deinit() == .ok);14 defer std.debug.assert(debug_allocator.deinit() == .ok);
1315
14 var tmp = std.testing.tmpDir(.{});16 var threaded: std.Io.Threaded = .init(gpa, .{});
15 defer tmp.cleanup();17 defer threaded.deinit();
1618 const io = threaded.io();
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 }
4519
46 var buffer: [std.fs.max_path_bytes]u8 = undefined;20 var tmp = tmpDir(io, .{});
47 const given = try std.posix.readlink(symlink_name, buffer[0..]);21 defer tmp.cleanup(io);
48 try std.testing.expectEqualStrings(target_name, given);
49}
5022
51fn getLinkInfo(fd: std.posix.fd_t) !struct { std.posix.ino_t, std.posix.nlink_t } {23 // Want to test relative paths, so cd into the tmpdir for these tests
52 if (builtin.target.os.tag == .linux) {24 try std.process.setCurrentDir(io, tmp.dir);
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 }
6325
64 const st = try std.posix.fstat(fd);26 try test_link(io, tmp);
65 return .{ st.ino, st.nlink };
66}27}
6728
68fn test_link(tmp: std.testing.TmpDir) !void {29fn test_link(io: Io, tmp: TmpDir) !void {
69 switch (builtin.target.os.tag) {30 switch (builtin.target.os.tag) {
70 .linux, .illumos => {},31 .linux, .illumos => {},
71 else => return,32 else => return,
...@@ -74,29 +35,67 @@ fn test_link(tmp: std.testing.TmpDir) !void {...@@ -74,29 +35,67 @@ fn test_link(tmp: std.testing.TmpDir) !void {
74 const target_name = "link-target";35 const target_name = "link-target";
75 const link_name = "newlink";36 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
79 // Test 1: create the relative link from inside tmp40 // 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
82 // Verify43 // Verify
83 const efd = try tmp.dir.openFile(target_name, .{});44 const efd = try tmp.dir.openFile(io, target_name, .{});
84 defer efd.close();45 defer efd.close(io);
8546
86 const nfd = try tmp.dir.openFile(link_name, .{});47 const nfd = try tmp.dir.openFile(io, link_name, .{});
87 defer nfd.close();48 defer nfd.close(io);
8849
89 {50 {
90 const eino, _ = try getLinkInfo(efd.handle);51 const e_stat = try efd.stat(io);
91 const nino, const nlink = try getLinkInfo(nfd.handle);52 const n_stat = try nfd.stat(io);
92 try std.testing.expectEqual(eino, nino);53 try std.testing.expectEqual(e_stat.inode, n_stat.inode);
93 try std.testing.expectEqual(@as(std.posix.nlink_t, 2), nlink);54 try std.testing.expectEqual(2, n_stat.nlink);
94 }55 }
9556
96 // Test 2: Remove the link and see the stats update57 // 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);
98 {59 {
99 _, const elink = try getLinkInfo(efd.handle);60 const e_stat = try efd.stat(io);
100 try std.testing.expectEqual(@as(std.posix.nlink_t, 1), elink);61 try std.testing.expectEqual(1, e_stat.nlink);
101 }62 }
102}63}
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 {...@@ -8,7 +8,9 @@ pub fn main() !void {
8 if (args.len != 2) return error.BadUsage;8 if (args.len != 2) return error.BadUsage;
9 const path = args[1];9 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;
12}14}
1315
14const std = @import("std");16const std = @import("std");
test/standalone/run_output_caching/main.zig+4-3
...@@ -1,10 +1,11 @@...@@ -1,10 +1,11 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main() !void {
4 const io = std.Io.Threaded.global_single_threaded.ioBasic();
4 var args = try std.process.argsWithAllocator(std.heap.page_allocator);5 var args = try std.process.argsWithAllocator(std.heap.page_allocator);
5 _ = args.skip();6 _ = args.skip();
6 const filename = args.next().?;7 const filename = args.next().?;
7 const file = try std.fs.cwd().createFile(filename, .{});8 const file = try std.Io.Dir.cwd().createFile(io, filename, .{});
8 defer file.close();9 defer file.close(io);
9 try file.writeAll(filename);10 try file.writeStreamingAll(io, filename);
10}11}
test/standalone/run_output_paths/create_file.zig+4-3
...@@ -1,16 +1,17 @@...@@ -1,16 +1,17 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main() !void {
4 const io = std.Io.Threaded.global_single_threaded.ioBasic();
4 var args = try std.process.argsWithAllocator(std.heap.page_allocator);5 var args = try std.process.argsWithAllocator(std.heap.page_allocator);
5 _ = args.skip();6 _ = args.skip();
6 const dir_name = args.next().?;7 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="))
8 dir_name["--dir=".len..]9 dir_name["--dir=".len..]
9 else10 else
10 dir_name, .{});11 dir_name, .{});
11 const file_name = args.next().?;12 const file_name = args.next().?;
12 const file = try dir.createFile(file_name, .{});13 const file = try dir.createFile(io, file_name, .{});
13 var file_writer = file.writer(&.{});14 var file_writer = file.writer(io, &.{});
14 try file_writer.interface.print(15 try file_writer.interface.print(
15 \\{s}16 \\{s}
16 \\{s}17 \\{s}
test/standalone/self_exe_symlink/build.zig-4
...@@ -9,10 +9,6 @@ pub fn build(b: *std.Build) void {...@@ -9,10 +9,6 @@ pub fn build(b: *std.Build) void {
9 const optimize: std.builtin.OptimizeMode = .Debug;9 const optimize: std.builtin.OptimizeMode = .Debug;
10 const target = b.graph.host;10 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
16 const main = b.addExecutable(.{12 const main = b.addExecutable(.{
17 .name = "main",13 .name = "main",
18 .root_module = b.createModule(.{14 .root_module = b.createModule(.{
test/standalone/self_exe_symlink/create-symlink.zig+4-1
...@@ -14,5 +14,8 @@ pub fn main() anyerror!void {...@@ -14,5 +14,8 @@ pub fn main() anyerror!void {
14 // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`.14 // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`.
15 const exe_rel_path = try std.fs.path.relative(allocator, std.fs.path.dirname(symlink_path) orelse ".", exe_path);15 const exe_rel_path = try std.fs.path.relative(allocator, std.fs.path.dirname(symlink_path) orelse ".", exe_path);
16 defer allocator.free(exe_rel_path);16 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, .{});
18}21}
test/standalone/self_exe_symlink/main.zig+13-8
...@@ -1,17 +1,22 @@...@@ -1,17 +1,22 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main() !void {
4 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;4 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
5 defer std.debug.assert(gpa.deinit() == .ok);5 defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks");
6 const allocator = gpa.allocator();6 const gpa = debug_allocator.allocator();
77
8 const self_path = try std.fs.selfExePathAlloc(allocator);8 var threaded: std.Io.Threaded = .init(gpa, .{});
9 defer allocator.free(self_path);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();
13 var buf: [std.fs.max_path_bytes]u8 = undefined;18 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
16 try std.testing.expectEqualStrings(self_exe_path, self_path);21 try std.testing.expectEqualStrings(self_exe_path, self_path);
17}22}
test/standalone/simple/cat/main.zig+7-7
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const fs = std.fs;2const Io = std.Io;
3const mem = std.mem;3const mem = std.mem;
4const warn = std.log.warn;4const warn = std.log.warn;
5const fatal = std.process.fatal;5const fatal = std.process.fatal;
...@@ -9,7 +9,7 @@ pub fn main() !void {...@@ -9,7 +9,7 @@ pub fn main() !void {
9 defer arena_instance.deinit();9 defer arena_instance.deinit();
10 const arena = arena_instance.allocator();10 const arena = arena_instance.allocator();
1111
12 var threaded: std.Io.Threaded = .init(arena);12 var threaded: std.Io.Threaded = .init(arena, .{});
13 defer threaded.deinit();13 defer threaded.deinit();
14 const io = threaded.io();14 const io = threaded.io();
1515
...@@ -18,11 +18,11 @@ pub fn main() !void {...@@ -18,11 +18,11 @@ pub fn main() !void {
18 const exe = args[0];18 const exe = args[0];
19 var catted_anything = false;19 var catted_anything = false;
20 var stdout_buffer: [4096]u8 = undefined;20 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);
22 const stdout = &stdout_writer.interface;22 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
27 for (args[1..]) |arg| {27 for (args[1..]) |arg| {
28 if (mem.eql(u8, arg, "-")) {28 if (mem.eql(u8, arg, "-")) {
...@@ -32,8 +32,8 @@ pub fn main() !void {...@@ -32,8 +32,8 @@ pub fn main() !void {
32 } else if (mem.startsWith(u8, arg, "-")) {32 } else if (mem.startsWith(u8, arg, "-")) {
33 return usage(exe);33 return usage(exe);
34 } else {34 } else {
35 const file = cwd.openFile(arg, .{}) catch |err| fatal("unable to open file: {t}\n", .{err});35 const file = cwd.openFile(io, arg, .{}) catch |err| fatal("unable to open file: {t}\n", .{err});
36 defer file.close();36 defer file.close(io);
3737
38 catted_anything = true;38 catted_anything = true;
39 var file_reader = file.reader(io, &.{});39 var file_reader = file.reader(io, &.{});
test/standalone/simple/guess_number/main.zig+24-9
...@@ -1,10 +1,23 @@...@@ -1,10 +1,23 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("std");2const std = @import("std");
33
4// See https://github.com/ziglang/zig/issues/24510
5// for the plan to simplify this code.
4pub fn main() !void {6pub 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, &.{});
6 const out = &stdout_writer.interface;16 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
9 try out.writeAll("Welcome to the Guess Number Game in Zig.\n");22 try out.writeAll("Welcome to the Guess Number Game in Zig.\n");
1023
...@@ -12,13 +25,15 @@ pub fn main() !void {...@@ -12,13 +25,15 @@ pub fn main() !void {
1225
13 while (true) {26 while (true) {
14 try out.writeAll("\nGuess a number between 1 and 100: ");27 try out.writeAll("\nGuess a number between 1 and 100: ");
15 var line_buf: [20]u8 = undefined;28 const untrimmed_line = in.takeSentinel('\n') catch |err| switch (err) {
16 const amt = try stdin.read(&line_buf);29 error.StreamTooLong => {
17 if (amt == line_buf.len) {30 try out.writeAll("Line too long.\n");
18 try out.writeAll("Input too long.\n");31 _ = try in.discardDelimiterInclusive('\n');
19 continue;32 continue;
20 }33 },
21 const line = std.mem.trimEnd(u8, line_buf[0..amt], "\r\n");34 else => |e| return e,
35 };
36 const line = std.mem.trimEnd(u8, untrimmed_line, "\r\n");
2237
23 const guess = std.fmt.parseUnsigned(u8, line, 10) catch {38 const guess = std.fmt.parseUnsigned(u8, line, 10) catch {
24 try out.writeAll("Invalid number.\n");39 try out.writeAll("Invalid number.\n");
test/standalone/simple/hello_world/hello.zig+11-1
...@@ -1,5 +1,15 @@...@@ -1,5 +1,15 @@
1const std = @import("std");1const std = @import("std");
22
3// See https://github.com/ziglang/zig/issues/24510
4// for the plan to simplify this code.
3pub fn main() !void {5pub 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");
5}15}
test/standalone/windows_argv/build.zig+1-1
...@@ -67,7 +67,7 @@ pub fn build(b: *std.Build) !void {...@@ -67,7 +67,7 @@ pub fn build(b: *std.Build) !void {
6767
68 // Only target the MSVC ABI if MSVC/Windows SDK is available68 // Only target the MSVC ABI if MSVC/Windows SDK is available
69 const has_msvc = has_msvc: {69 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) {
71 error.OutOfMemory => @panic("oom"),71 error.OutOfMemory => @panic("oom"),
72 else => break :has_msvc false,72 else => break :has_msvc false,
73 };73 };
test/standalone/windows_bat_args/echo-args.zig+3-1
...@@ -5,7 +5,9 @@ pub fn main() !void {...@@ -5,7 +5,9 @@ pub fn main() !void {
5 defer arena_state.deinit();5 defer arena_state.deinit();
6 const arena = arena_state.allocator();6 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, &.{});
9 const stdout = &stdout_writer.interface;11 const stdout = &stdout_writer.interface;
10 var args = try std.process.argsAlloc(arena);12 var args = try std.process.argsAlloc(arena);
11 for (args[1..], 1..) |arg, i| {13 for (args[1..], 1..) |arg, i| {
test/standalone/windows_bat_args/fuzz.zig+59-16
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
3const Allocator = std.mem.Allocator;5const Allocator = std.mem.Allocator;
46
5pub fn main() anyerror!void {7pub fn main() anyerror!void {
...@@ -7,6 +9,10 @@ pub fn main() anyerror!void {...@@ -7,6 +9,10 @@ pub fn main() anyerror!void {
7 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);9 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
8 const gpa = debug_alloc_inst.allocator();10 const gpa = debug_alloc_inst.allocator();
911
12 var threaded: Io.Threaded = .init(gpa, .{});
13 defer threaded.deinit();
14 const io = threaded.io();
15
10 var it = try std.process.argsWithAllocator(gpa);16 var it = try std.process.argsWithAllocator(gpa);
11 defer it.deinit();17 defer it.deinit();
12 _ = it.next() orelse unreachable; // skip binary name18 _ = it.next() orelse unreachable; // skip binary name
...@@ -36,11 +42,11 @@ pub fn main() anyerror!void {...@@ -36,11 +42,11 @@ pub fn main() anyerror!void {
36 std.debug.print("rand seed: {}\n", .{seed});42 std.debug.print("rand seed: {}\n", .{seed});
37 }43 }
3844
39 var tmp = std.testing.tmpDir(.{});45 var tmp = tmpDir(io, .{});
40 defer tmp.cleanup();46 defer tmp.cleanup(io);
4147
42 try tmp.dir.setAsCwd();48 try std.process.setCurrentDir(io, tmp.dir);
43 defer tmp.parent_dir.setAsCwd() catch {};49 defer std.process.setCurrentDir(io, tmp.parent_dir) catch {};
4450
45 // `child_exe_path_orig` might be relative; make it relative to our new cwd.51 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
46 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });52 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
...@@ -56,15 +62,15 @@ pub fn main() anyerror!void {...@@ -56,15 +62,15 @@ pub fn main() anyerror!void {
56 const preamble_len = buf.items.len;62 const preamble_len = buf.items.len;
5763
58 try buf.appendSlice(gpa, " %*");64 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 });
60 buf.shrinkRetainingCapacity(preamble_len);66 buf.shrinkRetainingCapacity(preamble_len);
6167
62 try buf.appendSlice(gpa, " %1 %2 %3 %4 %5 %6 %7 %8 %9");68 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 });
64 buf.shrinkRetainingCapacity(preamble_len);70 buf.shrinkRetainingCapacity(preamble_len);
6571
66 try buf.appendSlice(gpa, " \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\"");72 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 });
68 buf.shrinkRetainingCapacity(preamble_len);74 buf.shrinkRetainingCapacity(preamble_len);
6975
70 var i: u64 = 0;76 var i: u64 = 0;
...@@ -72,19 +78,19 @@ pub fn main() anyerror!void {...@@ -72,19 +78,19 @@ pub fn main() anyerror!void {
72 const rand_arg = try randomArg(gpa, rand);78 const rand_arg = try randomArg(gpa, rand);
73 defer gpa.free(rand_arg);79 defer gpa.free(rand_arg);
7480
75 try testExec(gpa, &.{rand_arg}, null);81 try testExec(gpa, io, &.{rand_arg}, null);
7682
77 i += 1;83 i += 1;
78 }84 }
79}85}
8086
81fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {87fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void {
82 try testExecBat(gpa, "args1.bat", args, env);88 try testExecBat(gpa, io, "args1.bat", args, env);
83 try testExecBat(gpa, "args2.bat", args, env);89 try testExecBat(gpa, io, "args2.bat", args, env);
84 try testExecBat(gpa, "args3.bat", args, env);90 try testExecBat(gpa, io, "args3.bat", args, env);
85}91}
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 {
88 const argv = try gpa.alloc([]const u8, 1 + args.len);94 const argv = try gpa.alloc([]const u8, 1 + args.len);
89 defer gpa.free(argv);95 defer gpa.free(argv);
90 argv[0] = bat;96 argv[0] = bat;
...@@ -92,8 +98,7 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8...@@ -92,8 +98,7 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8
9298
93 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");99 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
94100
95 const result = try std.process.Child.run(.{101 const result = try std.process.Child.run(gpa, io, .{
96 .allocator = gpa,
97 .env_map = env,102 .env_map = env,
98 .argv = argv,103 .argv = argv,
99 });104 });
...@@ -163,3 +168,41 @@ fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 {...@@ -163,3 +168,41 @@ fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 {
163168
164 return buf.toOwnedSlice(gpa);169 return buf.toOwnedSlice(gpa);
165}170}
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 @@...@@ -1,20 +1,25 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
3const Allocator = std.mem.Allocator;
24
3pub fn main() anyerror!void {5pub fn main() anyerror!void {
4 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;6 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;
5 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);7 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
6 const gpa = debug_alloc_inst.allocator();8 const gpa = debug_alloc_inst.allocator();
79
10 var threaded: Io.Threaded = .init(gpa, .{});
11 const io = threaded.io();
12
8 var it = try std.process.argsWithAllocator(gpa);13 var it = try std.process.argsWithAllocator(gpa);
9 defer it.deinit();14 defer it.deinit();
10 _ = it.next() orelse unreachable; // skip binary name15 _ = it.next() orelse unreachable; // skip binary name
11 const child_exe_path_orig = it.next() orelse unreachable;16 const child_exe_path_orig = it.next() orelse unreachable;
1217
13 var tmp = std.testing.tmpDir(.{});18 var tmp = tmpDir(io, .{});
14 defer tmp.cleanup();19 defer tmp.cleanup(io);
1520
16 try tmp.dir.setAsCwd();21 try std.process.setCurrentDir(io, tmp.dir);
17 defer tmp.parent_dir.setAsCwd() catch {};22 defer std.process.setCurrentDir(io, tmp.parent_dir) catch {};
1823
19 // `child_exe_path_orig` might be relative; make it relative to our new cwd.24 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
20 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });25 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
...@@ -30,53 +35,53 @@ pub fn main() anyerror!void {...@@ -30,53 +35,53 @@ pub fn main() anyerror!void {
30 const preamble_len = buf.items.len;35 const preamble_len = buf.items.len;
3136
32 try buf.appendSlice(gpa, " %*");37 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 });
34 buf.shrinkRetainingCapacity(preamble_len);39 buf.shrinkRetainingCapacity(preamble_len);
3540
36 try buf.appendSlice(gpa, " %1 %2 %3 %4 %5 %6 %7 %8 %9");41 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 });
38 buf.shrinkRetainingCapacity(preamble_len);43 buf.shrinkRetainingCapacity(preamble_len);
3944
40 try buf.appendSlice(gpa, " \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\"");45 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 });
42 buf.shrinkRetainingCapacity(preamble_len);47 buf.shrinkRetainingCapacity(preamble_len);
4348
44 // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs49 // 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"});50 try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\x00"});
46 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\n"});51 try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\n"});
47 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\r"});52 try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\r"});
48 try testExec(gpa, &.{ "a", "b" }, null);53 try testExec(gpa, io, &.{ "a", "b" }, null);
49 try testExec(gpa, &.{ "c is for cat", "d is for dog" }, null);54 try testExec(gpa, io, &.{ "c is for cat", "d is for dog" }, null);
50 try testExec(gpa, &.{ "\"", " \"" }, null);55 try testExec(gpa, io, &.{ "\"", " \"" }, null);
51 try testExec(gpa, &.{ "\\", "\\" }, null);56 try testExec(gpa, io, &.{ "\\", "\\" }, null);
52 try testExec(gpa, &.{">file.txt"}, null);57 try testExec(gpa, io, &.{">file.txt"}, null);
53 try testExec(gpa, &.{"whoami.exe"}, null);58 try testExec(gpa, io, &.{"whoami.exe"}, null);
54 try testExec(gpa, &.{"&a.exe"}, null);59 try testExec(gpa, io, &.{"&a.exe"}, null);
55 try testExec(gpa, &.{"&echo hello "}, null);60 try testExec(gpa, io, &.{"&echo hello "}, null);
56 try testExec(gpa, &.{ "&echo hello", "&whoami", ">file.txt" }, null);61 try testExec(gpa, io, &.{ "&echo hello", "&whoami", ">file.txt" }, null);
57 try testExec(gpa, &.{"!TMP!"}, null);62 try testExec(gpa, io, &.{"!TMP!"}, null);
58 try testExec(gpa, &.{"key=value"}, null);63 try testExec(gpa, io, &.{"key=value"}, null);
59 try testExec(gpa, &.{"\"key=value\""}, null);64 try testExec(gpa, io, &.{"\"key=value\""}, null);
60 try testExec(gpa, &.{"key = value"}, null);65 try testExec(gpa, io, &.{"key = value"}, null);
61 try testExec(gpa, &.{"key=[\"value\"]"}, null);66 try testExec(gpa, io, &.{"key=[\"value\"]"}, null);
62 try testExec(gpa, &.{ "", "a=b" }, null);67 try testExec(gpa, io, &.{ "", "a=b" }, null);
63 try testExec(gpa, &.{"key=\"foo bar\""}, null);68 try testExec(gpa, io, &.{"key=\"foo bar\""}, null);
64 try testExec(gpa, &.{"key=[\"my_value]"}, null);69 try testExec(gpa, io, &.{"key=[\"my_value]"}, null);
65 try testExec(gpa, &.{"key=[\"my_value\",\"other-value\"]"}, null);70 try testExec(gpa, io, &.{"key=[\"my_value\",\"other-value\"]"}, null);
66 try testExec(gpa, &.{"key\\=value"}, null);71 try testExec(gpa, io, &.{"key\\=value"}, null);
67 try testExec(gpa, &.{"key=\"&whoami\""}, null);72 try testExec(gpa, io, &.{"key=\"&whoami\""}, null);
68 try testExec(gpa, &.{"key=\"value\"=5"}, null);73 try testExec(gpa, io, &.{"key=\"value\"=5"}, null);
69 try testExec(gpa, &.{"key=[\">file.txt\"]"}, null);74 try testExec(gpa, io, &.{"key=[\">file.txt\"]"}, null);
70 try testExec(gpa, &.{"%hello"}, null);75 try testExec(gpa, io, &.{"%hello"}, null);
71 try testExec(gpa, &.{"%PATH%"}, null);76 try testExec(gpa, io, &.{"%PATH%"}, null);
72 try testExec(gpa, &.{"%%cd:~,%"}, null);77 try testExec(gpa, io, &.{"%%cd:~,%"}, null);
73 try testExec(gpa, &.{"%PATH%PATH%"}, null);78 try testExec(gpa, io, &.{"%PATH%PATH%"}, null);
74 try testExec(gpa, &.{"\">file.txt"}, null);79 try testExec(gpa, io, &.{"\">file.txt"}, null);
75 try testExec(gpa, &.{"abc\"&echo hello"}, null);80 try testExec(gpa, io, &.{"abc\"&echo hello"}, null);
76 try testExec(gpa, &.{"123\">file.txt"}, null);81 try testExec(gpa, io, &.{"123\">file.txt"}, null);
77 try testExec(gpa, &.{"\"&echo hello&whoami.exe"}, null);82 try testExec(gpa, io, &.{"\"&echo hello&whoami.exe"}, null);
78 try testExec(gpa, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);83 try testExec(gpa, io, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);
79 try testExec(gpa, &.{"&whoami.exe"}, null);84 try testExec(gpa, io, &.{"&whoami.exe"}, null);
8085
81 // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution.86 // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution.
82 // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped87 // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped
...@@ -94,14 +99,14 @@ pub fn main() anyerror!void {...@@ -94,14 +99,14 @@ pub fn main() anyerror!void {
94 // > "args1.bat .. "99 // > "args1.bat .. "
95 // '"args1.bat .. "' is not recognized as an internal or external command,100 // '"args1.bat .. "' is not recognized as an internal or external command,
96 // operable program or batch file.101 // 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));
98 const absolute_with_trailing = blk: {103 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);
100 defer gpa.free(absolute_path);105 defer gpa.free(absolute_path);
101 break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " });106 break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " });
102 };107 };
103 defer gpa.free(absolute_with_trailing);108 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
106 var env = env: {111 var env = env: {
107 var env = try std.process.getEnvMap(gpa);112 var env = try std.process.getEnvMap(gpa);
...@@ -115,23 +120,23 @@ pub fn main() anyerror!void {...@@ -115,23 +120,23 @@ pub fn main() anyerror!void {
115 break :env env;120 break :env env;
116 };121 };
117 defer env.deinit();122 defer env.deinit();
118 try testExec(gpa, &.{"%FOO%"}, &env);123 try testExec(gpa, io, &.{"%FOO%"}, &env);
119124
120 // Ensure that none of the `>file.txt`s have caused file.txt to be created125 // 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", .{}));
122}127}
123128
124fn testExecError(err: anyerror, gpa: std.mem.Allocator, args: []const []const u8) !void {129fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8) !void {
125 return std.testing.expectError(err, testExec(gpa, args, null));130 return std.testing.expectError(err, testExec(gpa, io, args, null));
126}131}
127132
128fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {133fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void {
129 try testExecBat(gpa, "args1.bat", args, env);134 try testExecBat(gpa, io, "args1.bat", args, env);
130 try testExecBat(gpa, "args2.bat", args, env);135 try testExecBat(gpa, io, "args2.bat", args, env);
131 try testExecBat(gpa, "args3.bat", args, env);136 try testExecBat(gpa, io, "args3.bat", args, env);
132}137}
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 {
135 const argv = try gpa.alloc([]const u8, 1 + args.len);140 const argv = try gpa.alloc([]const u8, 1 + args.len);
136 defer gpa.free(argv);141 defer gpa.free(argv);
137 argv[0] = bat;142 argv[0] = bat;
...@@ -139,8 +144,7 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8...@@ -139,8 +144,7 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8
139144
140 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");145 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
141146
142 const result = try std.process.Child.run(.{147 const result = try std.process.Child.run(gpa, io, .{
143 .allocator = gpa,
144 .env_map = env,148 .env_map = env,
145 .argv = argv,149 .argv = argv,
146 });150 });
...@@ -160,3 +164,41 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8...@@ -160,3 +164,41 @@ fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8
160 i += 1;164 i += 1;
161 }165 }
162}166}
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 {...@@ -10,10 +10,14 @@ pub fn main() !void {
1010
11 if (args.len < 3) return error.MissingArgs;11 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
13 const relative = try std.fs.path.relative(allocator, args[1], args[2]);17 const relative = try std.fs.path.relative(allocator, args[1], args[2]);
14 defer allocator.free(relative);18 defer allocator.free(relative);
1519
16 var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});20 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
17 const stdout = &stdout_writer.interface;21 const stdout = &stdout_writer.interface;
18 try stdout.writeAll(relative);22 try stdout.writeAll(relative);
19}23}
test/standalone/windows_paths/test.zig+25-22
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
23
3pub fn main() anyerror!void {4pub fn main() anyerror!void {
4 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);5 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
...@@ -9,6 +10,8 @@ pub fn main() anyerror!void {...@@ -9,6 +10,8 @@ pub fn main() anyerror!void {
910
10 if (args.len < 2) return error.MissingArgs;11 if (args.len < 2) return error.MissingArgs;
1112
13 const io = std.Io.Threaded.global_single_threaded.ioBasic();
14
12 const exe_path = args[1];15 const exe_path = args[1];
1316
14 const cwd_path = try std.process.getCwdAlloc(arena);17 const cwd_path = try std.process.getCwdAlloc(arena);
...@@ -33,39 +36,39 @@ pub fn main() anyerror!void {...@@ -33,39 +36,39 @@ pub fn main() anyerror!void {
3336
34 // With the special =X: environment variable set, drive-relative paths that37 // With the special =X: environment variable set, drive-relative paths that
35 // don't match the CWD's drive letter are resolved against that env var.38 // 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);39 try checkRelative(arena, io, "..\\..\\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);40 try checkRelative(arena, io, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &alt_drive_env_map);
3841
39 // Without that environment variable set, drive-relative paths that don't match the42 // Without that environment variable set, drive-relative paths that don't match the
40 // CWD's drive letter are resolved against the root of the drive.43 // 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);44 try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);
42 try checkRelative(arena, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);45 try checkRelative(arena, io, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);
4346
44 // Bare drive-relative path with no components47 // Bare drive-relative path with no components
45 try checkRelative(arena, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &empty_env);48 try checkRelative(arena, io, "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);49 try checkRelative(arena, io, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &empty_env);
4750
48 // Bare drive-relative path with no components, drive-CWD set51 // 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);52 try checkRelative(arena, io, "..\\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);53 try checkRelative(arena, io, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &alt_drive_env_map);
5154
52 // Bare drive-relative path relative to the CWD should be equivalent if drive-CWD is set55 // 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);56 try checkRelative(arena, io, "", &.{ 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);57 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, null, &alt_drive_env_map);
5558
56 // Bare drive-relative should always be equivalent to itself59 // 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);60 try checkRelative(arena, io, "", &.{ 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);61 try checkRelative(arena, io, "", &.{ 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);62 try checkRelative(arena, io, "", &.{ 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);63 try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env);
61 }64 }
6265
63 if (parsed_cwd_path.kind == .unc_absolute) {66 if (parsed_cwd_path.kind == .unc_absolute) {
64 const drive_abs_path = try std.fmt.allocPrint(arena, "{c}:\\foo\\bar", .{alt_drive_letter});67 const drive_abs_path = try std.fmt.allocPrint(arena, "{c}:\\foo\\bar", .{alt_drive_letter});
6568
66 {69 {
67 try checkRelative(arena, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, null, &empty_env);70 try checkRelative(arena, io, 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);71 try checkRelative(arena, io, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, null, &empty_env);
69 }72 }
70 } else if (parsed_cwd_path.kind == .drive_absolute) {73 } else if (parsed_cwd_path.kind == .drive_absolute) {
71 const cur_drive_letter = parsed_cwd_path.root[0];74 const cur_drive_letter = parsed_cwd_path.root[0];
...@@ -73,14 +76,14 @@ pub fn main() anyerror!void {...@@ -73,14 +76,14 @@ pub fn main() anyerror!void {
73 const unc_cwd = try std.fmt.allocPrint(arena, "\\\\127.0.0.1\\{c}$\\{s}", .{ cur_drive_letter, path_beyond_root });76 const unc_cwd = try std.fmt.allocPrint(arena, "\\\\127.0.0.1\\{c}$\\{s}", .{ cur_drive_letter, path_beyond_root });
7477
75 {78 {
76 try checkRelative(arena, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, null, &empty_env);79 try checkRelative(arena, io, 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);80 try checkRelative(arena, io, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, null, &empty_env);
78 }81 }
79 {82 {
80 const drive_abs = cwd_path;83 const drive_abs = cwd_path;
81 const drive_rel = parsed_cwd_path.root[0..2];84 const drive_rel = parsed_cwd_path.root[0..2];
82 try checkRelative(arena, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);85 try checkRelative(arena, io, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env);
83 try checkRelative(arena, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);86 try checkRelative(arena, io, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env);
84 }87 }
85 } else {88 } else {
86 return error.UnexpectedPathType;89 return error.UnexpectedPathType;
...@@ -89,13 +92,13 @@ pub fn main() anyerror!void {...@@ -89,13 +92,13 @@ pub fn main() anyerror!void {
8992
90fn checkRelative(93fn checkRelative(
91 allocator: std.mem.Allocator,94 allocator: std.mem.Allocator,
95 io: Io,
92 expected_stdout: []const u8,96 expected_stdout: []const u8,
93 argv: []const []const u8,97 argv: []const []const u8,
94 cwd: ?[]const u8,98 cwd: ?[]const u8,
95 env_map: ?*const std.process.EnvMap,99 env_map: ?*const std.process.EnvMap,
96) !void {100) !void {
97 const result = try std.process.Child.run(.{101 const result = try std.process.Child.run(allocator, io, .{
98 .allocator = allocator,
99 .argv = argv,102 .argv = argv,
100 .cwd = cwd,103 .cwd = cwd,
101 .env_map = env_map,104 .env_map = env_map,
test/standalone/windows_spawn/hello.zig+2-1
...@@ -1,7 +1,8 @@...@@ -1,7 +1,8 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub 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, &.{});
5 const stdout = &stdout_writer.interface;6 const stdout = &stdout_writer.interface;
6 try stdout.writeAll("hello from exe\n");7 try stdout.writeAll("hello from exe\n");
7}8}
test/standalone/windows_spawn/main.zig+119-76
...@@ -1,29 +1,35 @@...@@ -1,29 +1,35 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
3const Allocator = std.mem.Allocator;
24
3const windows = std.os.windows;5const windows = std.os.windows;
4const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral;6const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral;
57
6pub fn main() anyerror!void {8pub fn main() anyerror!void {
7 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;9 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
8 defer if (gpa.deinit() == .leak) @panic("found memory leaks");10 defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks");
9 const allocator = gpa.allocator();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);
12 defer it.deinit();18 defer it.deinit();
13 _ = it.next() orelse unreachable; // skip binary name19 _ = it.next() orelse unreachable; // skip binary name
14 const hello_exe_cache_path = it.next() orelse unreachable;20 const hello_exe_cache_path = it.next() orelse unreachable;
1521
16 var tmp = std.testing.tmpDir(.{});22 var tmp = tmpDir(io, .{});
17 defer tmp.cleanup();23 defer tmp.cleanup(io);
1824
19 const tmp_absolute_path = try tmp.dir.realpathAlloc(allocator, ".");25 const tmp_absolute_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
20 defer allocator.free(tmp_absolute_path);26 defer gpa.free(tmp_absolute_path);
21 const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(allocator, tmp_absolute_path);27 const tmp_absolute_path_w = try std.unicode.utf8ToUtf16LeAllocZ(gpa, tmp_absolute_path);
22 defer allocator.free(tmp_absolute_path_w);28 defer gpa.free(tmp_absolute_path_w);
23 const cwd_absolute_path = try std.fs.cwd().realpathAlloc(allocator, ".");29 const cwd_absolute_path = try Io.Dir.cwd().realPathFileAlloc(io, ".", gpa);
24 defer allocator.free(cwd_absolute_path);30 defer gpa.free(cwd_absolute_path);
25 const tmp_relative_path = try std.fs.path.relative(allocator, cwd_absolute_path, tmp_absolute_path);31 const tmp_relative_path = try std.fs.path.relative(gpa, cwd_absolute_path, tmp_absolute_path);
26 defer allocator.free(tmp_relative_path);32 defer gpa.free(tmp_relative_path);
2733
28 // Clear PATH34 // Clear PATH
29 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(35 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
...@@ -38,10 +44,10 @@ pub fn main() anyerror!void {...@@ -38,10 +44,10 @@ pub fn main() anyerror!void {
38 ) == windows.TRUE);44 ) == windows.TRUE);
3945
40 // No PATH, so it should fail to find anything not in the cwd46 // 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
43 // make sure we don't get error.BadPath traversing out of cwd with a relative path49 // 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
46 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(52 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
47 utf16Literal("PATH"),53 utf16Literal("PATH"),
...@@ -49,82 +55,82 @@ pub fn main() anyerror!void {...@@ -49,82 +55,82 @@ pub fn main() anyerror!void {
49 ) == windows.TRUE);55 ) == windows.TRUE);
5056
51 // Move hello.exe into the tmp dir which is now added to the path57 // 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
54 // with extension should find the .exe (case insensitive)60 // 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");
56 // without extension should find the .exe (case insensitive)62 // 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");
58 // with invalid cwd64 // 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
61 // now add a .bat67 // 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" });
63 // and a .cmd69 // 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
66 // with extension should find the .bat (case insensitive)72 // 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");
68 // with extension should find the .cmd (case insensitive)74 // 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");
70 // without extension should find the .exe (since its first in PATHEXT)76 // 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
73 // now rename the exe to not have an extension79 // 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
76 // with extension should now fail82 // with extension should now fail
77 try testExecError(error.FileNotFound, allocator, "hello.exe");83 try testExecError(error.FileNotFound, gpa, io, "hello.exe");
78 // without extension should succeed (case insensitive)84 // 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");87 try tmp.dir.createDir(io, "something", .default_dir);
82 try renameExe(tmp.dir, "hello", "something/hello.exe");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" });90 const relative_path_no_ext = try std.fs.path.join(gpa, &.{ tmp_relative_path, "something/hello" });
85 defer allocator.free(relative_path_no_ext);91 defer gpa.free(relative_path_no_ext);
8692
87 // Giving a full relative path to something/hello should work93 // 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");
89 // But commands with path separators get excluded from PATH searching, so this will fail95 // 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
92 // Now that .BAT is the first PATHEXT that should be found, this should succeed98 // 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
95 // Add a hello.exe that is not a valid executable101 // 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
98 // Trying to execute it with extension will give InvalidExe. This is a special104 // Trying to execute it with extension will give InvalidExe. This is a special
99 // case for .EXE extensions, where if they ever try to get executed but they are105 // case for .EXE extensions, where if they ever try to get executed but they are
100 // invalid, that gets treated as a fatal error wherever they are found and InvalidExe106 // invalid, that gets treated as a fatal error wherever they are found and InvalidExe
101 // is returned immediately.107 // is returned immediately.
102 try testExecError(error.InvalidExe, allocator, "hello.exe");108 try testExecError(error.InvalidExe, gpa, io, "hello.exe");
103 // Same thing applies to the command with no extension--even though there is a109 // Same thing applies to the command with no extension--even though there is a
104 // hello.bat that could be executed, it should stop after it tries executing110 // hello.bat that could be executed, it should stop after it tries executing
105 // hello.exe and getting InvalidExe.111 // hello.exe and getting InvalidExe.
106 try testExecError(error.InvalidExe, allocator, "hello");112 try testExecError(error.InvalidExe, gpa, io, "hello");
107113
108 // If we now rename hello.exe to have no extension, it will behave differently114 // 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
111 // Now, trying to execute it without an extension should treat InvalidExe as recoverable117 // Now, trying to execute it without an extension should treat InvalidExe as recoverable
112 // and skip over it and find hello.bat and execute that118 // 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
115 // If we rename the invalid exe to something else121 // If we rename the invalid exe to something else
116 try renameExe(tmp.dir, "hello", "goodbye");122 try renameExe(tmp.dir, io, "hello", "goodbye");
117 // Then we should now get FileNotFound when trying to execute 'goodbye',123 // Then we should now get FileNotFound when trying to execute 'goodbye',
118 // since that is what the original error will be after searching for 'goodbye'124 // since that is what the original error will be after searching for 'goodbye'
119 // in the cwd. It will try to execute 'goodbye' from the PATH but the InvalidExe error125 // in the cwd. It will try to execute 'goodbye' from the PATH but the InvalidExe error
120 // should be ignored in this case.126 // should be ignored in this case.
121 try testExecError(error.FileNotFound, allocator, "goodbye");127 try testExecError(error.FileNotFound, gpa, io, "goodbye");
122128
123 // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir129 // 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();130 try std.process.setCurrentDir(io, tmp.dir);
125 defer tmp.parent_dir.setAsCwd() catch {};131 defer std.process.setCurrentDir(io, tmp.parent_dir) catch {};
126 const something_subdir_abs_path = try std.mem.concatWithSentinel(allocator, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0);132 const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0);
127 defer allocator.free(something_subdir_abs_path);133 defer gpa.free(something_subdir_abs_path);
128134
129 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(135 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
130 utf16Literal("PATH"),136 utf16Literal("PATH"),
...@@ -133,37 +139,37 @@ pub fn main() anyerror!void {...@@ -133,37 +139,37 @@ pub fn main() anyerror!void {
133139
134 // Now trying to execute goodbye should give error.InvalidExe since it's the original140 // Now trying to execute goodbye should give error.InvalidExe since it's the original
135 // error that we got when trying within the cwd141 // 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
138 // hello should still find the .bat144 // 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
141 // If we rename something/hello.exe to something/goodbye.exe147 // 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");
143 // And try to execute goodbye, then the one in something should be found149 // And try to execute goodbye, then the one in something should be found
144 // since the one in cwd is an invalid executable150 // 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
147 // If we use an absolute path to execute the invalid goodbye153 // 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" });154 const goodbye_abs_path = try std.mem.join(gpa, "\\", &.{ tmp_absolute_path, "goodbye" });
149 defer allocator.free(goodbye_abs_path);155 defer gpa.free(goodbye_abs_path);
150 // then the PATH should not be searched and we should get InvalidExe156 // 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
153 // If we try to exec but provide a cwd that is an absolute path, the PATH159 // If we try to exec but provide a cwd that is an absolute path, the PATH
154 // should still be searched and the goodbye.exe in something should be found.160 // 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
157 // introduce some extra path separators into the path which is dealt with inside the spawn call.163 // introduce some extra path separators into the path which is dealt with inside the spawn call.
158 const denormed_something_subdir_size = std.mem.replacementSize(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"));164 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);166 const denormed_something_subdir_abs_path = try gpa.allocSentinel(u16, denormed_something_subdir_size, 0);
161 defer allocator.free(denormed_something_subdir_abs_path);167 defer gpa.free(denormed_something_subdir_abs_path);
162168
163 _ = std.mem.replace(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"), denormed_something_subdir_abs_path);169 _ = 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);171 const denormed_something_subdir_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, denormed_something_subdir_abs_path);
166 defer allocator.free(denormed_something_subdir_wtf8);172 defer gpa.free(denormed_something_subdir_wtf8);
167173
168 // clear the path to ensure that the match comes from the cwd174 // clear the path to ensure that the match comes from the cwd
169 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(175 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
...@@ -171,21 +177,21 @@ pub fn main() anyerror!void {...@@ -171,21 +177,21 @@ pub fn main() anyerror!void {
171 null,177 null,
172 ) == windows.TRUE);178 ) == 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
176 // normalization should also work if the non-normalized path is found in the PATH var.182 // normalization should also work if the non-normalized path is found in the PATH var.
177 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(183 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
178 utf16Literal("PATH"),184 utf16Literal("PATH"),
179 denormed_something_subdir_abs_path,185 denormed_something_subdir_abs_path,
180 ) == windows.TRUE);186 ) == windows.TRUE);
181 try testExec(allocator, "goodbye", "hello from exe\n");187 try testExec(gpa, io, "goodbye", "hello from exe\n");
182188
183 // now make sure we can launch executables "outside" of the cwd189 // now make sure we can launch executables "outside" of the cwd
184 var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{});190 var subdir_cwd = try tmp.dir.openDir(io, denormed_something_subdir_wtf8, .{});
185 defer subdir_cwd.close();191 defer subdir_cwd.close(io);
186192
187 try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe");193 try renameExe(tmp.dir, io, "something/goodbye.exe", "hello.exe");
188 try subdir_cwd.setAsCwd();194 try std.process.setCurrentDir(io, subdir_cwd);
189195
190 // clear the PATH again196 // clear the PATH again
191 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(197 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
...@@ -194,33 +200,32 @@ pub fn main() anyerror!void {...@@ -194,33 +200,32 @@ pub fn main() anyerror!void {
194 ) == windows.TRUE);200 ) == windows.TRUE);
195201
196 // while we're at it make sure non-windows separators work fine202 // 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");
198}204}
199205
200fn testExecError(err: anyerror, allocator: std.mem.Allocator, command: []const u8) !void {206fn testExecError(err: anyerror, gpa: Allocator, io: Io, command: []const u8) !void {
201 return std.testing.expectError(err, testExec(allocator, command, ""));207 return std.testing.expectError(err, testExec(gpa, io, command, ""));
202}208}
203209
204fn testExec(allocator: std.mem.Allocator, command: []const u8, expected_stdout: []const u8) !void {210fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []const u8) !void {
205 return testExecWithCwd(allocator, command, null, expected_stdout);211 return testExecWithCwd(gpa, io, command, null, expected_stdout);
206}212}
207213
208fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void {214fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void {
209 const result = try std.process.Child.run(.{215 const result = try std.process.Child.run(gpa, io, .{
210 .allocator = allocator,
211 .argv = &[_][]const u8{command},216 .argv = &[_][]const u8{command},
212 .cwd = cwd,217 .cwd = cwd,
213 });218 });
214 defer allocator.free(result.stdout);219 defer gpa.free(result.stdout);
215 defer allocator.free(result.stderr);220 defer gpa.free(result.stderr);
216221
217 try std.testing.expectEqualStrings("", result.stderr);222 try std.testing.expectEqualStrings("", result.stderr);
218 try std.testing.expectEqualStrings(expected_stdout, result.stdout);223 try std.testing.expectEqualStrings(expected_stdout, result.stdout);
219}224}
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 {
222 var attempt: u5 = 0;227 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) {
224 error.AccessDenied => {229 error.AccessDenied => {
225 if (attempt == 13) return error.AccessDenied;230 if (attempt == 13) return error.AccessDenied;
226 // give the kernel a chance to finish closing the executable handle231 // 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...@@ -231,3 +236,41 @@ fn renameExe(dir: std.fs.Dir, old_sub_path: []const u8, new_sub_path: []const u8
231 else => |e| return e,236 else => |e| return e,
232 };237 };
233}238}
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: {...@@ -187,29 +187,30 @@ const test_targets = blk: {
187 .link_libc = true,187 .link_libc = true,
188 },188 },
189189
190 .{190 // Disabled due to https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9203351
191 .target = .{191 //.{
192 .cpu_arch = .aarch64,192 // .target = .{
193 .os_tag = .linux,193 // .cpu_arch = .aarch64,
194 .abi = .none,194 // .os_tag = .linux,
195 },195 // .abi = .none,
196 .use_llvm = false,196 // },
197 .use_lld = false,197 // .use_llvm = false,
198 .optimize_mode = .ReleaseFast,198 // .use_lld = false,
199 .strip = true,199 // .optimize_mode = .ReleaseFast,
200 },200 // .strip = true,
201 .{201 //},
202 .target = .{202 //.{
203 .cpu_arch = .aarch64,203 // .target = .{
204 .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.neoverse_n1 },204 // .cpu_arch = .aarch64,
205 .os_tag = .linux,205 // .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.neoverse_n1 },
206 .abi = .none,206 // .os_tag = .linux,
207 },207 // .abi = .none,
208 .use_llvm = false,208 // },
209 .use_lld = false,209 // .use_llvm = false,
210 .optimize_mode = .ReleaseFast,210 // .use_lld = false,
211 .strip = true,211 // .optimize_mode = .ReleaseFast,
212 },212 // .strip = true,
213 //},
213214
214 .{215 .{
215 .target = .{216 .target = .{
...@@ -1204,17 +1205,18 @@ const test_targets = blk: {...@@ -1204,17 +1205,18 @@ const test_targets = blk: {
1204 },1205 },
1205 },1206 },
12061207
1207 .{1208 // Disabled due to https://codeberg.org/ziglang/zig/pulls/30232#issuecomment-9203351
1208 .target = .{1209 //.{
1209 .cpu_arch = .aarch64,1210 // .target = .{
1210 .os_tag = .macos,1211 // .cpu_arch = .aarch64,
1211 .abi = .none,1212 // .os_tag = .macos,
1212 },1213 // .abi = .none,
1213 .use_llvm = false,1214 // },
1214 .use_lld = false,1215 // .use_llvm = false,
1215 .optimize_mode = .ReleaseFast,1216 // .use_lld = false,
1216 .strip = true,1217 // .optimize_mode = .ReleaseFast,
1217 },1218 // .strip = true,
1219 //},
12181220
1219 .{1221 .{
1220 .target = .{1222 .target = .{
...@@ -2024,6 +2026,7 @@ pub fn addLinkTests(...@@ -2024,6 +2026,7 @@ pub fn addLinkTests(
2024pub fn addCliTests(b: *std.Build) *Step {2026pub fn addCliTests(b: *std.Build) *Step {
2025 const step = b.step("test-cli", "Test the command line interface");2027 const step = b.step("test-cli", "Test the command line interface");
2026 const s = std.fs.path.sep_str;2028 const s = std.fs.path.sep_str;
2029 const io = b.graph.io;
20272030
2028 {2031 {
2029 // Test `zig init`.2032 // Test `zig init`.
...@@ -2132,14 +2135,14 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2132,14 +2135,14 @@ pub fn addCliTests(b: *std.Build) *Step {
2132 const tmp_path = b.makeTempPath();2135 const tmp_path = b.makeTempPath();
2133 const unformatted_code = " // no reason for indent";2136 const unformatted_code = " // no reason for indent";
21342137
2135 var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled");2138 var dir = std.Io.Dir.cwd().openDir(io, tmp_path, .{}) catch @panic("unhandled");
2136 defer dir.close();2139 defer dir.close(io);
2137 dir.writeFile(.{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled");2140 dir.writeFile(io, .{ .sub_path = "fmt1.zig", .data = unformatted_code }) catch @panic("unhandled");
2138 dir.writeFile(.{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled");2141 dir.writeFile(io, .{ .sub_path = "fmt2.zig", .data = unformatted_code }) catch @panic("unhandled");
2139 dir.makeDir("subdir") catch @panic("unhandled");2142 dir.createDir(io, "subdir", .default_dir) catch @panic("unhandled");
2140 var subdir = dir.openDir("subdir", .{}) catch @panic("unhandled");2143 var subdir = dir.openDir(io, "subdir", .{}) catch @panic("unhandled");
2141 defer subdir.close();2144 defer subdir.close(io);
2142 subdir.writeFile(.{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled");2145 subdir.writeFile(io, .{ .sub_path = "fmt3.zig", .data = unformatted_code }) catch @panic("unhandled");
21432146
2144 // Test zig fmt affecting only the appropriate files.2147 // Test zig fmt affecting only the appropriate files.
2145 const run1 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "fmt1.zig" });2148 const run1 = b.addSystemCommand(&.{ b.graph.zig_exe, "fmt", "fmt1.zig" });
...@@ -2629,11 +2632,12 @@ pub fn addCases(...@@ -2629,11 +2632,12 @@ pub fn addCases(
2629) !void {2632) !void {
2630 const arena = b.allocator;2633 const arena = b.allocator;
2631 const gpa = b.allocator;2634 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 });2639 var dir = try b.build_root.handle.openDir(io, "test/cases", .{ .iterate = true });
2636 defer dir.close();2640 defer dir.close(io);
26372641
2638 cases.addFromDir(dir, b);2642 cases.addFromDir(dir, b);
2639 try @import("cases.zig").addCases(&cases, build_options, b);2643 try @import("cases.zig").addCases(&cases, build_options, b);
...@@ -2678,7 +2682,9 @@ pub fn addDebuggerTests(b: *std.Build, options: DebuggerContext.Options) ?*Step...@@ -2678,7 +2682,9 @@ pub fn addDebuggerTests(b: *std.Build, options: DebuggerContext.Options) ?*Step
2678 return step;2682 return step;
2679}2683}
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
2682 const incr_check = b.addExecutable(.{2688 const incr_check = b.addExecutable(.{
2683 .name = "incr-check",2689 .name = "incr-check",
2684 .root_module = b.createModule(.{2690 .root_module = b.createModule(.{
...@@ -2688,12 +2694,17 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {...@@ -2688,12 +2694,17 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {
2688 }),2694 }),
2689 });2695 });
26902696
2691 var dir = try b.build_root.handle.openDir("test/incremental", .{ .iterate = true });2697 var dir = try b.build_root.handle.openDir(io, "test/incremental", .{ .iterate = true });
2692 defer dir.close();2698 defer dir.close(io);
26932699
2694 var it = try dir.walk(b.graph.arena);2700 var it = try dir.walk(b.graph.arena);
2695 while (try it.next()) |entry| {2701 while (try it.next(io)) |entry| {
2696 if (entry.kind != .file) continue;2702 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
2698 const run = b.addRunArtifact(incr_check);2709 const run = b.addRunArtifact(incr_check);
2699 run.setName(b.fmt("incr-check '{s}'", .{entry.basename}));2710 run.setName(b.fmt("incr-check '{s}'", .{entry.basename}));
...@@ -2702,6 +2713,11 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {...@@ -2702,6 +2713,11 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {
2702 run.addFileArg(b.path("test/incremental/").path(b, entry.path));2713 run.addFileArg(b.path("test/incremental/").path(b, entry.path));
2703 run.addArgs(&.{ "--zig-lib-dir", b.fmt("{f}", .{b.graph.zig_lib_directory}) });2714 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
2705 run.addCheck(.{ .expect_term = .{ .Exited = 0 } });2721 run.addCheck(.{ .expect_term = .{ .Exited = 0 } });
27062722
2707 test_step.dependOn(&run.step);2723 test_step.dependOn(&run.step);
tools/docgen.zig+18-16
...@@ -1,6 +1,8 @@...@@ -1,6 +1,8 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
3const fs = std.fs;2
3const std = @import("std");
4const Io = std.Io;
5const Dir = std.Io.Dir;
4const process = std.process;6const process = std.process;
5const Progress = std.Progress;7const Progress = std.Progress;
6const print = std.debug.print;8const print = std.debug.print;
...@@ -8,7 +10,6 @@ const mem = std.mem;...@@ -8,7 +10,6 @@ const mem = std.mem;
8const testing = std.testing;10const testing = std.testing;
9const Allocator = std.mem.Allocator;11const Allocator = std.mem.Allocator;
10const ArrayList = std.ArrayList;12const ArrayList = std.ArrayList;
11const getExternalExecutor = std.zig.system.getExternalExecutor;
12const fatal = std.process.fatal;13const fatal = std.process.fatal;
13const Writer = std.Io.Writer;14const Writer = std.Io.Writer;
1415
...@@ -38,7 +39,7 @@ pub fn main() !void {...@@ -38,7 +39,7 @@ pub fn main() !void {
3839
39 const gpa = arena;40 const gpa = arena;
4041
41 var threaded: std.Io.Threaded = .init(gpa);42 var threaded: std.Io.Threaded = .init(gpa, .{});
42 defer threaded.deinit();43 defer threaded.deinit();
43 const io = threaded.io();44 const io = threaded.io();
4445
...@@ -49,7 +50,7 @@ pub fn main() !void {...@@ -49,7 +50,7 @@ pub fn main() !void {
49 while (args_it.next()) |arg| {50 while (args_it.next()) |arg| {
50 if (mem.startsWith(u8, arg, "-")) {51 if (mem.startsWith(u8, arg, "-")) {
51 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {52 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);
53 process.exit(0);54 process.exit(0);
54 } else if (mem.eql(u8, arg, "--code-dir")) {55 } else if (mem.eql(u8, arg, "--code-dir")) {
55 if (args_it.next()) |param| {56 if (args_it.next()) |param| {
...@@ -72,16 +73,16 @@ pub fn main() !void {...@@ -72,16 +73,16 @@ pub fn main() !void {
72 const output_path = opt_output orelse fatal("missing output file", .{});73 const output_path = opt_output orelse fatal("missing output file", .{});
73 const code_dir_path = opt_code_dir orelse fatal("missing --code-dir argument", .{});74 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 var in_file = try Dir.cwd().openFile(io, input_path, .{});
76 defer in_file.close();77 defer in_file.close(io);
7778
78 var out_file = try fs.cwd().createFile(output_path, .{});79 var out_file = try Dir.cwd().createFile(io, output_path, .{});
79 defer out_file.close();80 defer out_file.close(io);
80 var out_file_buffer: [4096]u8 = undefined;81 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 var code_dir = try Dir.cwd().openDir(io, code_dir_path, .{});
84 defer code_dir.close();85 defer code_dir.close(io);
8586
86 var in_file_reader = in_file.reader(io, &.{});87 var in_file_reader = in_file.reader(io, &.{});
87 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .limited(max_doc_file_size));88 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .limited(max_doc_file_size));
...@@ -89,7 +90,7 @@ pub fn main() !void {...@@ -89,7 +90,7 @@ pub fn main() !void {
89 var tokenizer = Tokenizer.init(input_path, input_file_bytes);90 var tokenizer = Tokenizer.init(input_path, input_file_bytes);
90 var toc = try genToc(arena, &tokenizer);91 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);
93 try out_file_writer.end();94 try out_file_writer.end();
94}95}
9596
...@@ -988,9 +989,10 @@ fn printShell(out: *Writer, shell_content: []const u8, escape: bool) !void {...@@ -988,9 +989,10 @@ fn printShell(out: *Writer, shell_content: []const u8, escape: bool) !void {
988989
989fn genHtml(990fn genHtml(
990 allocator: Allocator,991 allocator: Allocator,
992 io: Io,
991 tokenizer: *Tokenizer,993 tokenizer: *Tokenizer,
992 toc: *Toc,994 toc: *Toc,
993 code_dir: std.fs.Dir,995 code_dir: Dir,
994 out: *Writer,996 out: *Writer,
995) !void {997) !void {
996 for (toc.nodes) |node| {998 for (toc.nodes) |node| {
...@@ -1042,11 +1044,11 @@ fn genHtml(...@@ -1042,11 +1044,11 @@ fn genHtml(
1042 },1044 },
1043 .Code => |code| {1045 .Code => |code| {
1044 const out_basename = try std.fmt.allocPrint(allocator, "{s}.out", .{1046 const out_basename = try std.fmt.allocPrint(allocator, "{s}.out", .{
1045 fs.path.stem(code.name),1047 Dir.path.stem(code.name),
1046 });1048 });
1047 defer allocator.free(out_basename);1049 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| {
1050 return parseError(tokenizer, code.token, "unable to open '{s}': {t}", .{ out_basename, err });1052 return parseError(tokenizer, code.token, "unable to open '{s}': {t}", .{ out_basename, err });
1051 };1053 };
1052 defer allocator.free(contents);1054 defer allocator.free(contents);
tools/doctest.zig+29-34
...@@ -2,10 +2,10 @@ const builtin = @import("builtin");...@@ -2,10 +2,10 @@ const builtin = @import("builtin");
22
3const std = @import("std");3const std = @import("std");
4const Io = std.Io;4const Io = std.Io;
5const Dir = std.Io.Dir;
5const Writer = std.Io.Writer;6const Writer = std.Io.Writer;
6const fatal = std.process.fatal;7const fatal = std.process.fatal;
7const mem = std.mem;8const mem = std.mem;
8const fs = std.fs;
9const process = std.process;9const process = std.process;
10const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
11const testing = std.testing;11const testing = std.testing;
...@@ -40,7 +40,7 @@ pub fn main() !void {...@@ -40,7 +40,7 @@ pub fn main() !void {
4040
41 const gpa = arena;41 const gpa = arena;
4242
43 var threaded: std.Io.Threaded = .init(gpa);43 var threaded: std.Io.Threaded = .init(gpa, .{});
44 defer threaded.deinit();44 defer threaded.deinit();
45 const io = threaded.io();45 const io = threaded.io();
4646
...@@ -53,7 +53,7 @@ pub fn main() !void {...@@ -53,7 +53,7 @@ pub fn main() !void {
53 while (args_it.next()) |arg| {53 while (args_it.next()) |arg| {
54 if (mem.startsWith(u8, arg, "-")) {54 if (mem.startsWith(u8, arg, "-")) {
55 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {55 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);
57 process.exit(0);57 process.exit(0);
58 } else if (mem.eql(u8, arg, "-i")) {58 } else if (mem.eql(u8, arg, "-i")) {
59 opt_input = args_it.next() orelse fatal("expected parameter after -i", .{});59 opt_input = args_it.next() orelse fatal("expected parameter after -i", .{});
...@@ -78,37 +78,37 @@ pub fn main() !void {...@@ -78,37 +78,37 @@ pub fn main() !void {
78 const zig_path = opt_zig orelse fatal("missing zig compiler path (--zig)", .{});78 const zig_path = opt_zig orelse fatal("missing zig compiler path (--zig)", .{});
79 const cache_root = opt_cache_root orelse fatal("missing cache root path (--cache-root)", .{});79 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)));
82 const code = try parseManifest(arena, source_bytes);82 const code = try parseManifest(arena, source_bytes);
83 const source = stripManifest(source_bytes);83 const source = stripManifest(source_bytes);
8484
85 const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{85 const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{
86 cache_root, std.crypto.random.int(u64),86 cache_root, std.crypto.random.int(u64),
87 });87 });
88 fs.cwd().makePath(tmp_dir_path) catch |err|88 Dir.cwd().createDirPath(io, tmp_dir_path) catch |err|
89 fatal("unable to create tmp dir '{s}': {s}", .{ tmp_dir_path, @errorName(err) });89 fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err });
90 defer fs.cwd().deleteTree(tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {s}", .{90 defer Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{
91 tmp_dir_path, @errorName(err),91 tmp_dir_path, err,
92 });92 });
9393
94 var out_file = try fs.cwd().createFile(output_path, .{});94 var out_file = try Dir.cwd().createFile(io, output_path, .{});
95 defer out_file.close();95 defer out_file.close(io);
96 var out_file_buffer: [4096]u8 = undefined;96 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
99 const out = &out_file_writer.interface;99 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));
102 try printOutput(102 try printOutput(
103 arena,103 arena,
104 io,104 io,
105 out,105 out,
106 code,106 code,
107 tmp_dir_path,107 tmp_dir_path,
108 try std.fs.path.relative(arena, tmp_dir_path, zig_path),108 try Dir.path.relative(arena, tmp_dir_path, zig_path),
109 try std.fs.path.relative(arena, tmp_dir_path, input_path),109 try Dir.path.relative(arena, tmp_dir_path, input_path),
110 if (opt_zig_lib_dir) |zig_lib_dir|110 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)
112 else112 else
113 null,113 null,
114 );114 );
...@@ -141,7 +141,7 @@ fn printOutput(...@@ -141,7 +141,7 @@ fn printOutput(
141 defer shell_buffer.deinit();141 defer shell_buffer.deinit();
142 const shell_out = &shell_buffer.writer;142 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
146 switch (code.id) {146 switch (code.id) {
147 .exe => |expected_outcome| code_block: {147 .exe => |expected_outcome| code_block: {
...@@ -201,8 +201,7 @@ fn printOutput(...@@ -201,8 +201,7 @@ fn printOutput(
201 try shell_out.print("\n", .{});201 try shell_out.print("\n", .{});
202202
203 if (expected_outcome == .build_fail) {203 if (expected_outcome == .build_fail) {
204 const result = try process.Child.run(.{204 const result = try process.Child.run(arena, io, .{
205 .allocator = arena,
206 .argv = build_args.items,205 .argv = build_args.items,
207 .cwd = tmp_dir_path,206 .cwd = tmp_dir_path,
208 .env_map = &env_map,207 .env_map = &env_map,
...@@ -227,7 +226,7 @@ fn printOutput(...@@ -227,7 +226,7 @@ fn printOutput(
227 try shell_out.writeAll(colored_stderr);226 try shell_out.writeAll(colored_stderr);
228 break :code_block;227 break :code_block;
229 }228 }
230 const exec_result = run(arena, &env_map, tmp_dir_path, build_args.items) catch229 const exec_result = run(arena, io, &env_map, tmp_dir_path, build_args.items) catch
231 fatal("example failed to compile", .{});230 fatal("example failed to compile", .{});
232231
233 if (code.verbose_cimport) {232 if (code.verbose_cimport) {
...@@ -258,8 +257,7 @@ fn printOutput(...@@ -258,8 +257,7 @@ fn printOutput(
258 var exited_with_signal = false;257 var exited_with_signal = false;
259258
260 const result = if (expected_outcome == .fail) blk: {259 const result = if (expected_outcome == .fail) blk: {
261 const result = try process.Child.run(.{260 const result = try process.Child.run(arena, io, .{
262 .allocator = arena,
263 .argv = run_args,261 .argv = run_args,
264 .env_map = &env_map,262 .env_map = &env_map,
265 .cwd = tmp_dir_path,263 .cwd = tmp_dir_path,
...@@ -278,7 +276,7 @@ fn printOutput(...@@ -278,7 +276,7 @@ fn printOutput(
278 }276 }
279 break :blk result;277 break :blk result;
280 } else blk: {278 } else blk: {
281 break :blk run(arena, &env_map, tmp_dir_path, run_args) catch279 break :blk run(arena, io, &env_map, tmp_dir_path, run_args) catch
282 fatal("example crashed", .{});280 fatal("example crashed", .{});
283 };281 };
284282
...@@ -327,7 +325,7 @@ fn printOutput(...@@ -327,7 +325,7 @@ fn printOutput(
327 .arch_os_abi = triple,325 .arch_os_abi = triple,
328 });326 });
329 const target = try std.zig.system.resolveTargetQuery(io, target_query);327 const target = try std.zig.system.resolveTargetQuery(io, target_query);
330 switch (getExternalExecutor(&host, &target, .{328 switch (getExternalExecutor(io, &host, &target, .{
331 .link_libc = code.link_libc,329 .link_libc = code.link_libc,
332 })) {330 })) {
333 .native => {},331 .native => {},
...@@ -347,7 +345,7 @@ fn printOutput(...@@ -347,7 +345,7 @@ fn printOutput(
347 }345 }
348 }346 }
349347
350 const result = run(arena, &env_map, tmp_dir_path, test_args.items) catch348 const result = run(arena, io, &env_map, tmp_dir_path, test_args.items) catch
351 fatal("test failed", .{});349 fatal("test failed", .{});
352 const escaped_stderr = try escapeHtml(arena, result.stderr);350 const escaped_stderr = try escapeHtml(arena, result.stderr);
353 const escaped_stdout = try escapeHtml(arena, result.stdout);351 const escaped_stdout = try escapeHtml(arena, result.stdout);
...@@ -378,8 +376,7 @@ fn printOutput(...@@ -378,8 +376,7 @@ fn printOutput(
378 try test_args.append("-lc");376 try test_args.append("-lc");
379 try shell_out.print("-lc ", .{});377 try shell_out.print("-lc ", .{});
380 }378 }
381 const result = try process.Child.run(.{379 const result = try process.Child.run(arena, io, .{
382 .allocator = arena,
383 .argv = test_args.items,380 .argv = test_args.items,
384 .env_map = &env_map,381 .env_map = &env_map,
385 .cwd = tmp_dir_path,382 .cwd = tmp_dir_path,
...@@ -435,8 +432,7 @@ fn printOutput(...@@ -435,8 +432,7 @@ fn printOutput(
435 },432 },
436 }433 }
437434
438 const result = try process.Child.run(.{435 const result = try process.Child.run(arena, io, .{
439 .allocator = arena,
440 .argv = test_args.items,436 .argv = test_args.items,
441 .env_map = &env_map,437 .env_map = &env_map,
442 .cwd = tmp_dir_path,438 .cwd = tmp_dir_path,
...@@ -512,8 +508,7 @@ fn printOutput(...@@ -512,8 +508,7 @@ fn printOutput(
512 }508 }
513509
514 if (maybe_error_match) |error_match| {510 if (maybe_error_match) |error_match| {
515 const result = try process.Child.run(.{511 const result = try process.Child.run(arena, io, .{
516 .allocator = arena,
517 .argv = build_args.items,512 .argv = build_args.items,
518 .env_map = &env_map,513 .env_map = &env_map,
519 .cwd = tmp_dir_path,514 .cwd = tmp_dir_path,
...@@ -541,7 +536,7 @@ fn printOutput(...@@ -541,7 +536,7 @@ fn printOutput(
541 const colored_stderr = try termColor(arena, escaped_stderr);536 const colored_stderr = try termColor(arena, escaped_stderr);
542 try shell_out.print("\n{s} ", .{colored_stderr});537 try shell_out.print("\n{s} ", .{colored_stderr});
543 } else {538 } 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", .{});
545 }540 }
546 try shell_out.writeAll("\n");541 try shell_out.writeAll("\n");
547 },542 },
...@@ -600,7 +595,7 @@ fn printOutput(...@@ -600,7 +595,7 @@ fn printOutput(
600 try test_args.append(option);595 try test_args.append(option);
601 try shell_out.print("{s} ", .{option});596 try shell_out.print("{s} ", .{option});
602 }597 }
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", .{});
604 const escaped_stderr = try escapeHtml(arena, result.stderr);599 const escaped_stderr = try escapeHtml(arena, result.stderr);
605 const escaped_stdout = try escapeHtml(arena, result.stdout);600 const escaped_stdout = try escapeHtml(arena, result.stdout);
606 try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });601 try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });
...@@ -1132,12 +1127,12 @@ fn in(slice: []const u8, number: u8) bool {...@@ -1132,12 +1127,12 @@ fn in(slice: []const u8, number: u8) bool {
11321127
1133fn run(1128fn run(
1134 allocator: Allocator,1129 allocator: Allocator,
1130 io: Io,
1135 env_map: *process.EnvMap,1131 env_map: *process.EnvMap,
1136 cwd: []const u8,1132 cwd: []const u8,
1137 args: []const []const u8,1133 args: []const []const u8,
1138) !process.Child.RunResult {1134) !process.Child.RunResult {
1139 const result = try process.Child.run(.{1135 const result = try process.Child.run(allocator, io, .{
1140 .allocator = allocator,
1141 .argv = args,1136 .argv = args,
1142 .env_map = env_map,1137 .env_map = env_map,
1143 .cwd = cwd,1138 .cwd = cwd,
tools/dump-cov.zig+7-5
...@@ -2,6 +2,7 @@...@@ -2,6 +2,7 @@
2//! including file:line:column information for each PC.2//! including file:line:column information for each PC.
33
4const std = @import("std");4const std = @import("std");
5const Io = std.Io;
5const fatal = std.process.fatal;6const fatal = std.process.fatal;
6const Path = std.Build.Cache.Path;7const Path = std.Build.Cache.Path;
7const assert = std.debug.assert;8const assert = std.debug.assert;
...@@ -16,7 +17,7 @@ pub fn main() !void {...@@ -16,7 +17,7 @@ pub fn main() !void {
16 defer arena_instance.deinit();17 defer arena_instance.deinit();
17 const arena = arena_instance.allocator();18 const arena = arena_instance.allocator();
1819
19 var threaded: std.Io.Threaded = .init(gpa);20 var threaded: Io.Threaded = .init(gpa, .{});
20 defer threaded.deinit();21 defer threaded.deinit();
21 const io = threaded.io();22 const io = threaded.io();
2223
...@@ -51,12 +52,13 @@ pub fn main() !void {...@@ -51,12 +52,13 @@ pub fn main() !void {
51 var coverage: std.debug.Coverage = .init;52 var coverage: std.debug.Coverage = .init;
52 defer coverage.deinit(gpa);53 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 var debug_info = std.debug.Info.load(gpa, io, exe_path, &coverage, target.ofmt, target.cpu.arch) catch |err| {
55 fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) });56 fatal("failed to load debug info for {f}: {t}", .{ exe_path, err });
56 };57 };
57 defer debug_info.deinit(gpa);58 defer debug_info.deinit(gpa);
5859
59 const cov_bytes = cov_path.root_dir.handle.readFileAllocOptions(60 const cov_bytes = cov_path.root_dir.handle.readFileAllocOptions(
61 io,
60 cov_path.sub_path,62 cov_path.sub_path,
61 arena,63 arena,
62 .limited(1 << 30),64 .limited(1 << 30),
...@@ -67,7 +69,7 @@ pub fn main() !void {...@@ -67,7 +69,7 @@ pub fn main() !void {
67 };69 };
6870
69 var stdout_buffer: [4000]u8 = undefined;71 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);
71 const stdout = &stdout_writer.interface;73 const stdout = &stdout_writer.interface;
7274
73 const header: *SeenPcsHeader = @ptrCast(cov_bytes);75 const header: *SeenPcsHeader = @ptrCast(cov_bytes);
...@@ -83,7 +85,7 @@ pub fn main() !void {...@@ -83,7 +85,7 @@ pub fn main() !void {
83 std.mem.sortUnstable(usize, sorted_pcs, {}, std.sort.asc(usize));85 std.mem.sortUnstable(usize, sorted_pcs, {}, std.sort.asc(usize));
8486
85 const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, sorted_pcs.len);87 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
88 const seen_pcs = header.seenBits();90 const seen_pcs = header.seenBits();
8991
tools/fetch_them_macos_headers.zig+28-33
...@@ -1,10 +1,9 @@...@@ -1,10 +1,9 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;2const Io = std.Io;
3const fs = std.fs;3const Dir = std.Io.Dir;
4const mem = std.mem;4const mem = std.mem;
5const process = std.process;5const process = std.process;
6const assert = std.debug.assert;6const assert = std.debug.assert;
7const tmpDir = std.testing.tmpDir;
8const fatal = std.process.fatal;7const fatal = std.process.fatal;
9const info = std.log.info;8const info = std.log.info;
109
...@@ -86,19 +85,19 @@ pub fn main() anyerror!void {...@@ -86,19 +85,19 @@ pub fn main() anyerror!void {
86 } else try argv.append(arg);85 } else try argv.append(arg);
87 }86 }
8887
89 var threaded: Io.Threaded = .init(gpa);88 var threaded: Io.Threaded = .init(gpa, .{});
90 defer threaded.deinit();89 defer threaded.deinit();
91 const io = threaded.io();90 const io = threaded.io();
9291
93 const sysroot_path = sysroot orelse blk: {92 const sysroot_path = sysroot orelse blk: {
94 const target = try std.zig.system.resolveTargetQuery(io, .{});93 const target = try std.zig.system.resolveTargetQuery(io, .{});
95 break :blk std.zig.system.darwin.getSdk(allocator, &target) orelse94 break :blk std.zig.system.darwin.getSdk(allocator, io, &target) orelse
96 fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{});95 fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{});
97 };96 };
9897
99 var sdk_dir = try std.fs.cwd().openDir(sysroot_path, .{});98 var sdk_dir = try Dir.cwd().openDir(io, sysroot_path, .{});
100 defer sdk_dir.close();99 defer sdk_dir.close(io);
101 const sdk_info = try sdk_dir.readFileAlloc("SDKSettings.json", allocator, .limited(std.math.maxInt(u32)));100 const sdk_info = try sdk_dir.readFileAlloc(io, "SDKSettings.json", allocator, .limited(std.math.maxInt(u32)));
102101
103 const parsed_json = try std.json.parseFromSlice(struct {102 const parsed_json = try std.json.parseFromSlice(struct {
104 DefaultProperties: struct { MACOSX_DEPLOYMENT_TARGET: []const u8 },103 DefaultProperties: struct { MACOSX_DEPLOYMENT_TARGET: []const u8 },
...@@ -111,15 +110,14 @@ pub fn main() anyerror!void {...@@ -111,15 +110,14 @@ pub fn main() anyerror!void {
111 const os_ver: OsVer = @enumFromInt(version.major);110 const os_ver: OsVer = @enumFromInt(version.major);
112 info("found SDK deployment target macOS {f} aka '{t}'", .{ version, os_ver });111 info("found SDK deployment target macOS {f} aka '{t}'", .{ version, os_ver });
113112
114 var tmp = tmpDir(.{});113 const tmp_dir: Io.Dir = .cwd();
115 defer tmp.cleanup();
116114
117 for (&[_]Arch{ .aarch64, .x86_64 }) |arch| {115 for (&[_]Arch{ .aarch64, .x86_64 }) |arch| {
118 const target: Target = .{116 const target: Target = .{
119 .arch = arch,117 .arch = arch,
120 .os_ver = os_ver,118 .os_ver = os_ver,
121 };119 };
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);
123 }121 }
124}122}
125123
...@@ -130,13 +128,13 @@ fn fetchTarget(...@@ -130,13 +128,13 @@ fn fetchTarget(
130 sysroot: []const u8,128 sysroot: []const u8,
131 target: Target,129 target: Target,
132 ver: Version,130 ver: Version,
133 tmp: std.testing.TmpDir,131 tmp_dir: Io.Dir,
134) !void {132) !void {
135 const tmp_filename = "macos-headers";133 const tmp_filename = "macos-headers";
136 const headers_list_filename = "macos-headers.o.d";134 const headers_list_filename = "macos-headers.o.d";
137 const tmp_path = try tmp.dir.realpathAlloc(arena, ".");135 const tmp_path = try tmp_dir.realPathFileAlloc(io, ".", arena);
138 const tmp_file_path = try fs.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename });136 const tmp_file_path = try Dir.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 });137 const headers_list_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename });
140138
141 const macos_version = try std.fmt.allocPrint(arena, "-mmacosx-version-min={d}.{d}", .{139 const macos_version = try std.fmt.allocPrint(arena, "-mmacosx-version-min={d}.{d}", .{
142 ver.major,140 ver.major,
...@@ -166,20 +164,17 @@ fn fetchTarget(...@@ -166,20 +164,17 @@ fn fetchTarget(
166 });164 });
167 try cc_argv.appendSlice(args);165 try cc_argv.appendSlice(args);
168166
169 const res = try std.process.Child.run(.{167 const res = try std.process.Child.run(arena, io, .{ .argv = cc_argv.items });
170 .allocator = arena,
171 .argv = cc_argv.items,
172 });
173168
174 if (res.stderr.len != 0) {169 if (res.stderr.len != 0) {
175 std.log.err("{s}", .{res.stderr});170 std.log.err("{s}", .{res.stderr});
176 }171 }
177172
178 // Read in the contents of `macos-headers.o.d`173 // Read in the contents of `macos-headers.o.d`
179 const headers_list_file = try tmp.dir.openFile(headers_list_filename, .{});174 const headers_list_file = try tmp_dir.openFile(io, headers_list_filename, .{});
180 defer headers_list_file.close();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) {
183 error.FileNotFound,178 error.FileNotFound,
184 error.NotDir,179 error.NotDir,
185 => fatal("path '{s}' not found or not a directory. Did you accidentally delete it?", .{180 => fatal("path '{s}' not found or not a directory. Did you accidentally delete it?", .{
...@@ -187,13 +182,13 @@ fn fetchTarget(...@@ -187,13 +182,13 @@ fn fetchTarget(
187 }),182 }),
188 else => return err,183 else => return err,
189 };184 };
190 defer headers_dir.close();185 defer headers_dir.close(io);
191186
192 const dest_path = try target.fullName(arena);187 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, .{});190 var dest_dir = try headers_dir.createDirPathOpen(io, dest_path, .{});
196 var dirs = std.StringHashMap(fs.Dir).init(arena);191 var dirs = std.StringHashMap(Dir).init(arena);
197 try dirs.putNoClobber(".", dest_dir);192 try dirs.putNoClobber(".", dest_dir);
198193
199 var headers_list_file_reader = headers_list_file.reader(io, &.{});194 var headers_list_file_reader = headers_list_file.reader(io, &.{});
...@@ -206,25 +201,25 @@ fn fetchTarget(...@@ -206,25 +201,25 @@ fn fetchTarget(
206 if (mem.lastIndexOf(u8, line, prefix[0..])) |idx| {201 if (mem.lastIndexOf(u8, line, prefix[0..])) |idx| {
207 const out_rel_path = line[idx + prefix.len + 1 ..];202 const out_rel_path = line[idx + prefix.len + 1 ..];
208 const out_rel_path_stripped = mem.trim(u8, out_rel_path, " \\");203 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 ".";
210 const maybe_dir = try dirs.getOrPut(dirname);205 const maybe_dir = try dirs.getOrPut(dirname);
211 if (!maybe_dir.found_existing) {206 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, .{});
213 }208 }
214 const basename = fs.path.basename(out_rel_path_stripped);209 const basename = Dir.path.basename(out_rel_path_stripped);
215210
216 const line_stripped = mem.trim(u8, line, " \\");211 const line_stripped = mem.trim(u8, line, " \\");
217 const abs_dirname = fs.path.dirname(line_stripped).?;212 const abs_dirname = Dir.path.dirname(line_stripped).?;
218 var orig_subdir = try fs.cwd().openDir(abs_dirname, .{});213 var orig_subdir = try Dir.cwd().openDir(io, abs_dirname, .{});
219 defer orig_subdir.close();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, .{});
222 }217 }
223 }218 }
224219
225 var dir_it = dirs.iterator();220 var dir_it = dirs.iterator();
226 while (dir_it.next()) |entry| {221 while (dir_it.next()) |entry| {
227 entry.value_ptr.close();222 entry.value_ptr.close(io);
228 }223 }
229}224}
230225
tools/gen_macos_headers_c.zig+19-13
...@@ -1,8 +1,9 @@...@@ -1,8 +1,9 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
3const Dir = std.Io.Dir;
2const assert = std.debug.assert;4const assert = std.debug.assert;
3const info = std.log.info;5const info = std.log.info;
4const fatal = std.process.fatal;6const fatal = std.process.fatal;
5
6const Allocator = std.mem.Allocator;7const Allocator = std.mem.Allocator;
78
8var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};9var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
...@@ -20,6 +21,10 @@ pub fn main() anyerror!void {...@@ -20,6 +21,10 @@ pub fn main() anyerror!void {
20 defer arena_allocator.deinit();21 defer arena_allocator.deinit();
21 const arena = arena_allocator.allocator();22 const arena = arena_allocator.allocator();
2223
24 var threaded: Io.Threaded = .init(gpa, .{});
25 defer threaded.deinit();
26 const io = threaded.io();
27
23 const args = try std.process.argsAlloc(arena);28 const args = try std.process.argsAlloc(arena);
24 if (args.len == 1) fatal("no command or option specified", .{});29 if (args.len == 1) fatal("no command or option specified", .{});
2530
...@@ -33,10 +38,10 @@ pub fn main() anyerror!void {...@@ -33,10 +38,10 @@ pub fn main() anyerror!void {
3338
34 if (positionals.items.len != 1) fatal("expected one positional argument: [dir]", .{});39 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 });41 var dir = try Io.Dir.cwd().openDir(io, positionals.items[0], .{ .follow_symlinks = false });
37 defer dir.close();42 defer dir.close(io);
38 var paths = std.array_list.Managed([]const u8).init(arena);43 var paths = std.array_list.Managed([]const u8).init(arena);
39 try findHeaders(arena, dir, "", &paths);44 try findHeaders(arena, io, dir, "", &paths);
4045
41 const SortFn = struct {46 const SortFn = struct {
42 pub fn lessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool {47 pub fn lessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool {
...@@ -48,7 +53,7 @@ pub fn main() anyerror!void {...@@ -48,7 +53,7 @@ pub fn main() anyerror!void {
48 std.mem.sort([]const u8, paths.items, {}, SortFn.lessThan);53 std.mem.sort([]const u8, paths.items, {}, SortFn.lessThan);
4954
50 var buffer: [2000]u8 = undefined;55 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);
52 const w = &stdout_writer.interface;57 const w = &stdout_writer.interface;
53 try w.writeAll("#define _XOPEN_SOURCE\n");58 try w.writeAll("#define _XOPEN_SOURCE\n");
54 for (paths.items) |path| {59 for (paths.items) |path| {
...@@ -64,23 +69,24 @@ pub fn main() anyerror!void {...@@ -64,23 +69,24 @@ pub fn main() anyerror!void {
6469
65fn findHeaders(70fn findHeaders(
66 arena: Allocator,71 arena: Allocator,
67 dir: std.fs.Dir,72 io: Io,
73 dir: Dir,
68 prefix: []const u8,74 prefix: []const u8,
69 paths: *std.array_list.Managed([]const u8),75 paths: *std.array_list.Managed([]const u8),
70) anyerror!void {76) anyerror!void {
71 var it = dir.iterate();77 var it = dir.iterate();
72 while (try it.next()) |entry| {78 while (try it.next(io)) |entry| {
73 switch (entry.kind) {79 switch (entry.kind) {
74 .directory => {80 .directory => {
75 const path = try std.fs.path.join(arena, &.{ prefix, entry.name });81 const path = try Io.Dir.path.join(arena, &.{ prefix, entry.name });
76 var subdir = try dir.openDir(entry.name, .{ .follow_symlinks = false });82 var subdir = try dir.openDir(io, entry.name, .{ .follow_symlinks = false });
77 defer subdir.close();83 defer subdir.close(io);
78 try findHeaders(arena, subdir, path, paths);84 try findHeaders(arena, io, subdir, path, paths);
79 },85 },
80 .file, .sym_link => {86 .file, .sym_link => {
81 const ext = std.fs.path.extension(entry.name);87 const ext = Io.Dir.path.extension(entry.name);
82 if (!std.mem.eql(u8, ext, ".h")) continue;88 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 });
84 try paths.append(path);90 try paths.append(path);
85 },91 },
86 else => {},92 else => {},
tools/gen_outline_atomics.zig+6-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
34
4const AtomicOp = enum {5const AtomicOp = enum {
...@@ -15,10 +16,14 @@ pub fn main() !void {...@@ -15,10 +16,14 @@ pub fn main() !void {
15 defer arena_instance.deinit();16 defer arena_instance.deinit();
16 const arena = arena_instance.allocator();17 const arena = arena_instance.allocator();
1718
19 var threaded: std.Io.Threaded = .init(arena, .{});
20 defer threaded.deinit();
21 const io = threaded.io();
22
18 //const args = try std.process.argsAlloc(arena);23 //const args = try std.process.argsAlloc(arena);
1924
20 var stdout_buffer: [2000]u8 = undefined;25 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);
22 const w = &stdout_writer.interface;27 const w = &stdout_writer.interface;
2328
24 try w.writeAll(29 try w.writeAll(
tools/gen_spirv_spec.zig+23-16
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
4
3const g = @import("spirv/grammar.zig");5const g = @import("spirv/grammar.zig");
4const CoreRegistry = g.CoreRegistry;6const CoreRegistry = g.CoreRegistry;
5const ExtensionRegistry = g.ExtensionRegistry;7const ExtensionRegistry = g.ExtensionRegistry;
...@@ -63,24 +65,28 @@ pub fn main() !void {...@@ -63,24 +65,28 @@ pub fn main() !void {
63 usageAndExit(args[0], 1);65 usageAndExit(args[0], 1);
64 }66 }
6567
66 const json_path = try std.fs.path.join(allocator, &.{ args[1], "include/spirv/unified1/" });68 var threaded: std.Io.Threaded = .init(allocator, .{});
67 const dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true });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");
70 std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt);76 std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt);
7177
72 var exts = std.array_list.Managed(Extension).init(allocator);78 var exts = std.array_list.Managed(Extension).init(allocator);
7379
74 var it = dir.iterate();80 var it = dir.iterate();
75 while (try it.next()) |entry| {81 while (try it.next(io)) |entry| {
76 if (entry.kind != .file) {82 if (entry.kind != .file) {
77 continue;83 continue;
78 }84 }
7985
80 try readExtRegistry(&exts, dir, entry.name);86 try readExtRegistry(io, &exts, dir, entry.name);
81 }87 }
8288
83 try readExtRegistry(&exts, std.fs.cwd(), args[2]);89 try readExtRegistry(io, &exts, Io.Dir.cwd(), args[2]);
8490
85 var allocating: std.Io.Writer.Allocating = .init(allocator);91 var allocating: std.Io.Writer.Allocating = .init(allocator);
86 defer allocating.deinit();92 defer allocating.deinit();
...@@ -91,7 +97,7 @@ pub fn main() !void {...@@ -91,7 +97,7 @@ pub fn main() !void {
91 var tree = try std.zig.Ast.parse(allocator, output, .zig);97 var tree = try std.zig.Ast.parse(allocator, output, .zig);
9298
93 if (tree.errors.len != 0) {99 if (tree.errors.len != 0) {
94 try std.zig.printAstErrorsToStderr(allocator, tree, "", .auto);100 try std.zig.printAstErrorsToStderr(allocator, io, tree, "", .auto);
95 return;101 return;
96 }102 }
97103
...@@ -103,22 +109,22 @@ pub fn main() !void {...@@ -103,22 +109,22 @@ pub fn main() !void {
103 try wip_errors.addZirErrorMessages(zir, tree, output, "");109 try wip_errors.addZirErrorMessages(zir, tree, output, "");
104 var error_bundle = try wip_errors.toOwnedBundle("");110 var error_bundle = try wip_errors.toOwnedBundle("");
105 defer error_bundle.deinit(allocator);111 defer error_bundle.deinit(allocator);
106 error_bundle.renderToStdErr(.{}, .auto);112 try error_bundle.renderToStderr(io, .{}, .auto);
107 }113 }
108114
109 const formatted_output = try tree.renderAlloc(allocator);115 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);
111}117}
112118
113fn readExtRegistry(exts: *std.array_list.Managed(Extension), dir: std.fs.Dir, sub_path: []const u8) !void {119fn readExtRegistry(io: Io, exts: *std.array_list.Managed(Extension), dir: Io.Dir, sub_path: []const u8) !void {
114 const filename = std.fs.path.basename(sub_path);120 const filename = Io.Dir.path.basename(sub_path);
115 if (!std.mem.startsWith(u8, filename, "extinst.")) {121 if (!std.mem.startsWith(u8, filename, "extinst.")) {
116 return;122 return;
117 }123 }
118124
119 std.debug.assert(std.mem.endsWith(u8, filename, ".grammar.json"));125 std.debug.assert(std.mem.endsWith(u8, filename, ".grammar.json"));
120 const name = filename["extinst.".len .. filename.len - ".grammar.json".len];126 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
123 const set_name = set_names.get(name) orelse {129 const set_name = set_names.get(name) orelse {
124 std.log.info("ignored instruction set '{s}'", .{name});130 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...@@ -134,8 +140,8 @@ fn readExtRegistry(exts: *std.array_list.Managed(Extension), dir: std.fs.Dir, su
134 });140 });
135}141}
136142
137fn readRegistry(comptime RegistryType: type, dir: std.fs.Dir, path: []const u8) !RegistryType {143fn readRegistry(io: Io, comptime RegistryType: type, dir: Io.Dir, path: []const u8) !RegistryType {
138 const spec = try dir.readFileAlloc(path, allocator, .unlimited);144 const spec = try dir.readFileAlloc(io, path, allocator, .unlimited);
139 // Required for json parsing.145 // Required for json parsing.
140 // TODO: ALI146 // TODO: ALI
141 @setEvalBranchQuota(10000);147 @setEvalBranchQuota(10000);
...@@ -930,8 +936,9 @@ fn parseHexInt(text: []const u8) !u31 {...@@ -930,8 +936,9 @@ fn parseHexInt(text: []const u8) !u31 {
930}936}
931937
932fn usageAndExit(arg0: []const u8, code: u8) noreturn {938fn usageAndExit(arg0: []const u8, code: u8) noreturn {
933 const stderr, _ = std.debug.lockStderrWriter(&.{});939 const stderr = std.debug.lockStderr(&.{});
934 stderr.print(940 const w = &stderr.file_writer.interface;
941 w.print(
935 \\Usage: {s} <SPIRV-Headers repository path> <path/to/zig/src/codegen/spirv/extinst.zig.grammar.json>942 \\Usage: {s} <SPIRV-Headers repository path> <path/to/zig/src/codegen/spirv/extinst.zig.grammar.json>
936 \\943 \\
937 \\Generates Zig bindings for SPIR-V specifications found in the SPIRV-Headers944 \\Generates Zig bindings for SPIR-V specifications found in the SPIRV-Headers
tools/gen_stubs.zig+12-5
...@@ -55,12 +55,14 @@...@@ -55,12 +55,14 @@
55// - e.g. find a common previous symbol and put it after that one55// - e.g. find a common previous symbol and put it after that one
56// - they definitely need to go into the correct section56// - they definitely need to go into the correct section
5757
58const builtin = @import("builtin");
59const native_endian = builtin.cpu.arch.endian();
60
58const std = @import("std");61const std = @import("std");
59const builtin = std.builtin;62const Io = std.Io;
60const mem = std.mem;63const mem = std.mem;
61const log = std.log;64const log = std.log;
62const elf = std.elf;65const elf = std.elf;
63const native_endian = @import("builtin").cpu.arch.endian();
6466
65const Arch = enum {67const Arch = enum {
66 aarch64,68 aarch64,
...@@ -284,10 +286,14 @@ pub fn main() !void {...@@ -284,10 +286,14 @@ pub fn main() !void {
284 defer arena_instance.deinit();286 defer arena_instance.deinit();
285 const arena = arena_instance.allocator();287 const arena = arena_instance.allocator();
286288
289 var threaded: std.Io.Threaded = .init(arena, .{});
290 defer threaded.deinit();
291 const io = threaded.io();
292
287 const args = try std.process.argsAlloc(arena);293 const args = try std.process.argsAlloc(arena);
288 const build_all_path = args[1];294 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
292 var sym_table = std.StringArrayHashMap(MultiSym).init(arena);298 var sym_table = std.StringArrayHashMap(MultiSym).init(arena);
293 var sections = std.StringArrayHashMap(void).init(arena);299 var sections = std.StringArrayHashMap(void).init(arena);
...@@ -299,6 +305,7 @@ pub fn main() !void {...@@ -299,6 +305,7 @@ pub fn main() !void {
299305
300 // Read the ELF header.306 // Read the ELF header.
301 const elf_bytes = build_all_dir.readFileAllocOptions(307 const elf_bytes = build_all_dir.readFileAllocOptions(
308 io,
302 libc_so_path,309 libc_so_path,
303 arena,310 arena,
304 .limited(100 * 1024 * 1024),311 .limited(100 * 1024 * 1024),
...@@ -334,7 +341,7 @@ pub fn main() !void {...@@ -334,7 +341,7 @@ pub fn main() !void {
334 }341 }
335342
336 var stdout_buffer: [2000]u8 = undefined;343 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);
338 const stdout = &stdout_writer.interface;345 const stdout = &stdout_writer.interface;
339 try stdout.writeAll(346 try stdout.writeAll(
340 \\#ifdef PTR64347 \\#ifdef PTR64
...@@ -539,7 +546,7 @@ pub fn main() !void {...@@ -539,7 +546,7 @@ pub fn main() !void {
539 try stdout.flush();546 try stdout.flush();
540}547}
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 {
543 const arena = parse.arena;550 const arena = parse.arena;
544 const elf_bytes = parse.elf_bytes;551 const elf_bytes = parse.elf_bytes;
545 const header = parse.header;552 const header = parse.header;
tools/generate_JSONTestSuite.zig+9-4
...@@ -1,13 +1,18 @@...@@ -1,13 +1,18 @@
1// zig run this file inside the test_parsing/ directory of this repo: https://github.com/nst/JSONTestSuite1// zig run this file inside the test_parsing/ directory of this repo: https://github.com/nst/JSONTestSuite
22
3const std = @import("std");3const std = @import("std");
4const Io = std.Io;
45
5pub fn main() !void {6pub fn main() !void {
6 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;7 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
7 var allocator = gpa.allocator();8 var allocator = gpa.allocator();
89
10 var threaded: std.Io.Threaded = .init(allocator, .{});
11 defer threaded.deinit();
12 const io = threaded.io();
13
9 var stdout_buffer: [2000]u8 = undefined;14 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);
11 const output = &stdout_writer.interface;16 const output = &stdout_writer.interface;
12 try output.writeAll(17 try output.writeAll(
13 \\// This file was generated by _generate_JSONTestSuite.zig18 \\// This file was generated by _generate_JSONTestSuite.zig
...@@ -20,9 +25,9 @@ pub fn main() !void {...@@ -20,9 +25,9 @@ pub fn main() !void {
20 );25 );
2126
22 var names = std.array_list.Managed([]const u8).init(allocator);27 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 });
24 var it = cwd.iterate();29 var it = cwd.iterate();
25 while (try it.next()) |entry| {30 while (try it.next(io)) |entry| {
26 try names.append(try allocator.dupe(u8, entry.name));31 try names.append(try allocator.dupe(u8, entry.name));
27 }32 }
28 std.mem.sort([]const u8, names.items, {}, (struct {33 std.mem.sort([]const u8, names.items, {}, (struct {
...@@ -32,7 +37,7 @@ pub fn main() !void {...@@ -32,7 +37,7 @@ pub fn main() !void {
32 }).lessThan);37 }).lessThan);
3338
34 for (names.items) |name| {39 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));
36 try output.writeAll("test ");41 try output.writeAll("test ");
37 try writeString(output, name);42 try writeString(output, name);
38 try output.writeAll(" {\n try ");43 try output.writeAll(" {\n try ");
tools/generate_c_size_and_align_checks.zig+3-2
...@@ -7,6 +7,7 @@...@@ -7,6 +7,7 @@
7//! target.7//! target.
88
9const std = @import("std");9const std = @import("std");
10const Io = std.Io;
1011
11fn cName(ty: std.Target.CType) []const u8 {12fn cName(ty: std.Target.CType) []const u8 {
12 return switch (ty) {13 return switch (ty) {
...@@ -39,7 +40,7 @@ pub fn main() !void {...@@ -39,7 +40,7 @@ pub fn main() !void {
39 std.process.exit(1);40 std.process.exit(1);
40 }41 }
4142
42 var threaded: std.Io.Threaded = .init(gpa);43 var threaded: std.Io.Threaded = .init(gpa, .{});
43 defer threaded.deinit();44 defer threaded.deinit();
44 const io = threaded.io();45 const io = threaded.io();
4546
...@@ -47,7 +48,7 @@ pub fn main() !void {...@@ -47,7 +48,7 @@ pub fn main() !void {
47 const target = try std.zig.system.resolveTargetQuery(io, query);48 const target = try std.zig.system.resolveTargetQuery(io, query);
4849
49 var buffer: [2000]u8 = undefined;50 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);
51 const w = &stdout_writer.interface;52 const w = &stdout_writer.interface;
52 inline for (@typeInfo(std.Target.CType).@"enum".fields) |field| {53 inline for (@typeInfo(std.Target.CType).@"enum".fields) |field| {
53 const c_type: std.Target.CType = @enumFromInt(field.value);54 const c_type: std.Target.CType = @enumFromInt(field.value);
tools/generate_linux_syscalls.zig+11-7
...@@ -175,21 +175,25 @@ pub fn main() !void {...@@ -175,21 +175,25 @@ pub fn main() !void {
175 defer arena.deinit();175 defer arena.deinit();
176 const gpa = arena.allocator();176 const gpa = arena.allocator();
177177
178 var threaded: Io.Threaded = .init(gpa, .{});
179 defer threaded.deinit();
180 const io = threaded.io();
181
178 const args = try std.process.argsAlloc(gpa);182 const args = try std.process.argsAlloc(gpa);
179 if (args.len < 2 or mem.eql(u8, args[1], "--help")) {183 if (args.len < 2 or mem.eql(u8, args[1], "--help")) {
180 const w, _ = std.debug.lockStderrWriter(&.{});184 const stderr = std.debug.lockStderr(&.{});
181 defer std.debug.unlockStderrWriter();185 const w = &stderr.file_writer.interface;
182 usage(w, args[0]) catch std.process.exit(2);186 usage(w, args[0]) catch std.process.exit(2);
183 std.process.exit(1);187 std.process.exit(1);
184 }188 }
185 const linux_path = args[1];189 const linux_path = args[1];
186190
187 var stdout_buffer: [2048]u8 = undefined;191 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);
189 const stdout = &stdout_writer.interface;193 const stdout = &stdout_writer.interface;
190194
191 var linux_dir = try std.fs.cwd().openDir(linux_path, .{});195 var linux_dir = try Io.Dir.cwd().openDir(io, linux_path, .{});
192 defer linux_dir.close();196 defer linux_dir.close(io);
193197
194 // As of 6.11, the largest table is 24195 bytes.198 // As of 6.11, the largest table is 24195 bytes.
195 // 32k should be enough for now.199 // 32k should be enough for now.
...@@ -198,7 +202,7 @@ pub fn main() !void {...@@ -198,7 +202,7 @@ pub fn main() !void {
198202
199 // Fetch the kernel version from the Makefile variables.203 // Fetch the kernel version from the Makefile variables.
200 const version = blk: {204 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]);
202 var lines = mem.tokenizeScalar(u8, head, '\n');206 var lines = mem.tokenizeScalar(u8, head, '\n');
203 _ = lines.next(); // Skip SPDX identifier207 _ = lines.next(); // Skip SPDX identifier
204208
...@@ -221,7 +225,7 @@ pub fn main() !void {...@@ -221,7 +225,7 @@ pub fn main() !void {
221 , .{version});225 , .{version});
222226
223 for (architectures, 0..) |arch, i| {227 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) {
225 .generic => "scripts/syscall.tbl",229 .generic => "scripts/syscall.tbl",
226 .specific => |f| f,230 .specific => |f| f,
227 }, buf);231 }, buf);
tools/incr-check.zig+125-71
...@@ -1,9 +1,10 @@...@@ -1,9 +1,10 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;2const Io = std.Io;
3const Dir = std.Io.Dir;
3const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
4const Cache = std.Build.Cache;5const 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
8pub fn main() !void {9pub fn main() !void {
9 const fatal = std.process.fatal;10 const fatal = std.process.fatal;
...@@ -14,7 +15,7 @@ pub fn main() !void {...@@ -14,7 +15,7 @@ pub fn main() !void {
1415
15 const gpa = arena;16 const gpa = arena;
1617
17 var threaded: Io.Threaded = .init(gpa);18 var threaded: Io.Threaded = .init(gpa, .{});
18 defer threaded.deinit();19 defer threaded.deinit();
19 const io = threaded.io();20 const io = threaded.io();
2021
...@@ -22,27 +23,37 @@ pub fn main() !void {...@@ -22,27 +23,37 @@ pub fn main() !void {
22 var opt_input_file_name: ?[]const u8 = null;23 var opt_input_file_name: ?[]const u8 = null;
23 var opt_lib_dir: ?[]const u8 = null;24 var opt_lib_dir: ?[]const u8 = null;
24 var opt_cc_zig: ?[]const u8 = null;25 var opt_cc_zig: ?[]const u8 = null;
25 var debug_zcu = false;
26 var debug_dwarf = false;
27 var debug_link = false;
28 var preserve_tmp = false;26 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
30 var arg_it = try std.process.argsWithAllocator(arena);34 var arg_it = try std.process.argsWithAllocator(arena);
31 _ = arg_it.skip();35 _ = arg_it.skip();
32 while (arg_it.next()) |arg| {36 while (arg_it.next()) |arg| {
33 if (arg.len > 0 and arg[0] == '-') {37 if (arg.len > 0 and arg[0] == '-') {
34 if (std.mem.eql(u8, arg, "--zig-lib-dir")) {38 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});39 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")) {40 } else if (std.mem.eql(u8, arg, "--debug-log")) {
37 debug_zcu = true;41 try debug_log_args.append(
38 } else if (std.mem.eql(u8, arg, "--debug-dwarf")) {42 arena,
39 debug_dwarf = true;43 arg_it.next() orelse fatal("expected arg after --debug-log\n{s}", .{usage}),
40 } else if (std.mem.eql(u8, arg, "--debug-link")) {44 );
41 debug_link = true;
42 } else if (std.mem.eql(u8, arg, "--preserve-tmp")) {45 } else if (std.mem.eql(u8, arg, "--preserve-tmp")) {
43 preserve_tmp = true;46 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;
44 } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) {55 } 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});
46 } else {57 } else {
47 fatal("unknown option '{s}'\n{s}", .{ arg, usage });58 fatal("unknown option '{s}'\n{s}", .{ arg, usage });
48 }59 }
...@@ -59,7 +70,7 @@ pub fn main() !void {...@@ -59,7 +70,7 @@ pub fn main() !void {
59 const zig_exe = opt_zig_exe orelse fatal("missing path to zig\n{s}", .{usage});70 const zig_exe = opt_zig_exe orelse fatal("missing path to zig\n{s}", .{usage});
60 const input_file_name = opt_input_file_name orelse fatal("missing input file\n{s}", .{usage});71 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)));
63 const case = try Case.parse(arena, io, input_file_bytes);74 const case = try Case.parse(arena, io, input_file_bytes);
6475
65 // Check now: if there are any targets using the `cbe` backend, we need the lib dir.76 // Check now: if there are any targets using the `cbe` backend, we need the lib dir.
...@@ -71,31 +82,31 @@ pub fn main() !void {...@@ -71,31 +82,31 @@ pub fn main() !void {
71 }82 }
72 }83 }
7384
74 const prog_node = std.Progress.start(.{});85 const prog_node = std.Progress.start(io, .{});
75 defer prog_node.end();86 defer prog_node.end();
7687
77 const rand_int = std.crypto.random.int(u64);88 const rand_int = std.crypto.random.int(u64);
78 const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);89 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, .{});
80 defer {91 defer {
81 tmp_dir.close();92 tmp_dir.close(io);
82 if (!preserve_tmp) {93 if (!preserve_tmp) {
83 std.fs.cwd().deleteTree(tmp_dir_path) catch |err| {94 Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| {
84 std.log.warn("failed to delete tree '{s}': {s}", .{ tmp_dir_path, @errorName(err) });95 std.log.warn("failed to delete tree '{s}': {t}", .{ tmp_dir_path, err });
85 };96 };
86 }97 }
87 }98 }
8899
89 // Convert paths to be relative to the cwd of the subprocess.100 // 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);
91 const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir|102 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)
93 else104 else
94 null;105 null;
95106
96 const host = try std.zig.system.resolveTargetQuery(io, .{});107 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
100 for (case.targets) |target| {111 for (case.targets) |target| {
101 const target_prog_node = node: {112 const target_prog_node = node: {
...@@ -133,14 +144,8 @@ pub fn main() !void {...@@ -133,14 +144,8 @@ pub fn main() !void {
133 .llvm => try child_args.appendSlice(arena, &.{ "-fllvm", "-flld" }),144 .llvm => try child_args.appendSlice(arena, &.{ "-fllvm", "-flld" }),
134 .cbe => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }),145 .cbe => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }),
135 }146 }
136 if (debug_zcu) {147 for (debug_log_args.items) |arg| {
137 try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" });148 try child_args.appendSlice(arena, &.{ "--debug-log", arg });
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" });
144 }149 }
145 for (case.modules) |mod| {150 for (case.modules) |mod| {
146 try child_args.appendSlice(arena, &.{ "--dep", mod.name });151 try child_args.appendSlice(arena, &.{ "--dep", mod.name });
...@@ -164,7 +169,7 @@ pub fn main() !void {...@@ -164,7 +169,7 @@ pub fn main() !void {
164 var cc_child_args: std.ArrayList([]const u8) = .empty;169 var cc_child_args: std.ArrayList([]const u8) = .empty;
165 if (target.backend == .cbe) {170 if (target.backend == .cbe) {
166 const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe|171 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)
168 else173 else
169 resolved_zig_exe;174 resolved_zig_exe;
170175
...@@ -185,6 +190,7 @@ pub fn main() !void {...@@ -185,6 +190,7 @@ pub fn main() !void {
185190
186 var eval: Eval = .{191 var eval: Eval = .{
187 .arena = arena,192 .arena = arena,
193 .io = io,
188 .case = case,194 .case = case,
189 .host = host,195 .host = host,
190 .target = target,196 .target = target,
...@@ -194,11 +200,15 @@ pub fn main() !void {...@@ -194,11 +200,15 @@ pub fn main() !void {
194 .allow_stderr = debug_log_verbose,200 .allow_stderr = debug_log_verbose,
195 .preserve_tmp_on_fatal = preserve_tmp,201 .preserve_tmp_on_fatal = preserve_tmp,
196 .cc_child_args = &cc_child_args,202 .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,
197 };207 };
198208
199 try child.spawn();209 try child.spawn(io);
200 errdefer {210 errdefer {
201 _ = child.kill() catch {};211 _ = child.kill(io) catch {};
202 }212 }
203213
204 var poller = Io.poll(arena, Eval.StreamEnum, .{214 var poller = Io.poll(arena, Eval.StreamEnum, .{
...@@ -228,10 +238,11 @@ pub fn main() !void {...@@ -228,10 +238,11 @@ pub fn main() !void {
228238
229const Eval = struct {239const Eval = struct {
230 arena: Allocator,240 arena: Allocator,
241 io: Io,
231 host: std.Target,242 host: std.Target,
232 case: Case,243 case: Case,
233 target: Case.Target,244 target: Case.Target,
234 tmp_dir: std.fs.Dir,245 tmp_dir: Dir,
235 tmp_dir_path: []const u8,246 tmp_dir_path: []const u8,
236 child: *std.process.Child,247 child: *std.process.Child,
237 allow_stderr: bool,248 allow_stderr: bool,
...@@ -240,22 +251,28 @@ const Eval = struct {...@@ -240,22 +251,28 @@ const Eval = struct {
240 /// The arguments `out.c in.c` must be appended before spawning the subprocess.251 /// The arguments `out.c in.c` must be appended before spawning the subprocess.
241 cc_child_args: *std.ArrayList([]const u8),252 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
243 const StreamEnum = enum { stdout, stderr };259 const StreamEnum = enum { stdout, stderr };
244 const Poller = Io.Poller(StreamEnum);260 const Poller = Io.Poller(StreamEnum);
245261
246 /// Currently this function assumes the previous updates have already been written.262 /// Currently this function assumes the previous updates have already been written.
247 fn write(eval: *Eval, update: Case.Update) void {263 fn write(eval: *Eval, update: Case.Update) void {
264 const io = eval.io;
248 for (update.changes) |full_contents| {265 for (update.changes) |full_contents| {
249 eval.tmp_dir.writeFile(.{266 eval.tmp_dir.writeFile(io, .{
250 .sub_path = full_contents.name,267 .sub_path = full_contents.name,
251 .data = full_contents.bytes,268 .data = full_contents.bytes,
252 }) catch |err| {269 }) 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 });
254 };271 };
255 }272 }
256 for (update.deletes) |doomed_name| {273 for (update.deletes) |doomed_name| {
257 eval.tmp_dir.deleteFile(doomed_name) catch |err| {274 eval.tmp_dir.deleteFile(io, doomed_name) catch |err| {
258 eval.fatal("failed to delete '{s}': {s}", .{ doomed_name, @errorName(err) });275 eval.fatal("failed to delete '{s}': {t}", .{ doomed_name, err });
259 };276 };
260 }277 }
261 }278 }
...@@ -307,14 +324,14 @@ const Eval = struct {...@@ -307,14 +324,14 @@ const Eval = struct {
307 }324 }
308325
309 const digest = r.takeArray(Cache.bin_digest_len) catch unreachable;326 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
312 const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{329 const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
313 .root_name = "root", // corresponds to the module name "root"330 .root_name = "root", // corresponds to the module name "root"
314 .target = &eval.target.resolved,331 .target = &eval.target.resolved,
315 .output_mode = .Exe,332 .output_mode = .Exe,
316 });333 });
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
319 try eval.checkSuccessOutcome(update, bin_path, prog_node);336 try eval.checkSuccessOutcome(update, bin_path, prog_node);
320 // This message indicates the end of the update.337 // This message indicates the end of the update.
...@@ -338,11 +355,12 @@ const Eval = struct {...@@ -338,11 +355,12 @@ const Eval = struct {
338 }355 }
339356
340 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {357 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
358 const io = eval.io;
341 const expected = switch (update.outcome) {359 const expected = switch (update.outcome) {
342 .unknown => return,360 .unknown => return,
343 .compile_errors => |ce| ce,361 .compile_errors => |ce| ce,
344 .stdout, .exit_code => {362 .stdout, .exit_code => {
345 error_bundle.renderToStdErr(.{}, .auto);363 try error_bundle.renderToStderr(io, .{}, .auto);
346 eval.fatal("update '{s}': unexpected compile errors", .{update.name});364 eval.fatal("update '{s}': unexpected compile errors", .{update.name});
347 },365 },
348 };366 };
...@@ -351,7 +369,7 @@ const Eval = struct {...@@ -351,7 +369,7 @@ const Eval = struct {
351369
352 for (error_bundle.getMessages()) |err_idx| {370 for (error_bundle.getMessages()) |err_idx| {
353 if (expected_idx == expected.errors.len) {371 if (expected_idx == expected.errors.len) {
354 error_bundle.renderToStdErr(.{}, .auto);372 try error_bundle.renderToStderr(io, .{}, .auto);
355 eval.fatal("update '{s}': more errors than expected", .{update.name});373 eval.fatal("update '{s}': more errors than expected", .{update.name});
356 }374 }
357 try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], false, err_idx);375 try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], false, err_idx);
...@@ -359,7 +377,7 @@ const Eval = struct {...@@ -359,7 +377,7 @@ const Eval = struct {
359377
360 for (error_bundle.getNotes(err_idx)) |note_idx| {378 for (error_bundle.getNotes(err_idx)) |note_idx| {
361 if (expected_idx == expected.errors.len) {379 if (expected_idx == expected.errors.len) {
362 error_bundle.renderToStdErr(.{}, .auto);380 try error_bundle.renderToStderr(io, .{}, .auto);
363 eval.fatal("update '{s}': more error notes than expected", .{update.name});381 eval.fatal("update '{s}': more error notes than expected", .{update.name});
364 }382 }
365 try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], true, note_idx);383 try eval.checkOneError(update, error_bundle, expected.errors[expected_idx], true, note_idx);
...@@ -368,7 +386,7 @@ const Eval = struct {...@@ -368,7 +386,7 @@ const Eval = struct {
368 }386 }
369387
370 if (!std.mem.eql(u8, error_bundle.getCompileLogOutput(), expected.compile_log_output)) {388 if (!std.mem.eql(u8, error_bundle.getCompileLogOutput(), expected.compile_log_output)) {
371 error_bundle.renderToStdErr(.{}, .auto);389 try error_bundle.renderToStderr(io, .{}, .auto);
372 eval.fatal("update '{s}': unexpected compile log output", .{update.name});390 eval.fatal("update '{s}': unexpected compile log output", .{update.name});
373 }391 }
374 }392 }
...@@ -388,6 +406,8 @@ const Eval = struct {...@@ -388,6 +406,8 @@ const Eval = struct {
388 const src = eb.getSourceLocation(err.src_loc);406 const src = eb.getSourceLocation(err.src_loc);
389 const raw_filename = eb.nullTerminatedString(src.src_path);407 const raw_filename = eb.nullTerminatedString(src.src_path);
390408
409 const io = eval.io;
410
391 // We need to replace backslashes for consistency between platforms.411 // We need to replace backslashes for consistency between platforms.
392 const filename = name: {412 const filename = name: {
393 if (std.mem.indexOfScalar(u8, raw_filename, '\\') == null) break :name raw_filename;413 if (std.mem.indexOfScalar(u8, raw_filename, '\\') == null) break :name raw_filename;
...@@ -402,7 +422,7 @@ const Eval = struct {...@@ -402,7 +422,7 @@ const Eval = struct {
402 expected.column != src.column + 1 or422 expected.column != src.column + 1 or
403 !std.mem.eql(u8, expected.msg, msg))423 !std.mem.eql(u8, expected.msg, msg))
404 {424 {
405 eb.renderToStdErr(.{}, .auto);425 eb.renderToStderr(io, .{}, .auto) catch {};
406 eval.fatal("update '{s}': compile error did not match expected error", .{update.name});426 eval.fatal("update '{s}': compile error did not match expected error", .{update.name});
407 }427 }
408 }428 }
...@@ -429,8 +449,11 @@ const Eval = struct {...@@ -429,8 +449,11 @@ const Eval = struct {
429 },449 },
430 };450 };
431451
452 const io = eval.io;
453
432 var argv_buf: [2][]const u8 = undefined;454 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,
434 &eval.host,457 &eval.host,
435 &eval.target.resolved,458 &eval.target.resolved,
436 .{ .link_libc = eval.target.backend == .cbe },459 .{ .link_libc = eval.target.backend == .cbe },
...@@ -449,18 +472,48 @@ const Eval = struct {...@@ -449,18 +472,48 @@ const Eval = struct {
449 argv_buf[0] = binary_path;472 argv_buf[0] = binary_path;
450 break :argv .{ argv_buf[0..1], false };473 break :argv .{ argv_buf[0..1], false };
451 },474 },
452 .qemu, .wine, .wasmtime, .darling => |executor_cmd| argv: {475 .qemu => |executor_cmd| argv: {
453 argv_buf[0] = executor_cmd;476 if (eval.enable_qemu) {
454 argv_buf[1] = binary_path;477 argv_buf[0] = executor_cmd;
455 break :argv .{ argv_buf[0..2], true };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 }
456 },510 },
457 };511 };
458512
459 const run_prog_node = prog_node.start("run generated executable", 0);513 const run_prog_node = prog_node.start("run generated executable", 0);
460 defer run_prog_node.end();514 defer run_prog_node.end();
461515
462 const result = std.process.Child.run(.{516 const result = std.process.Child.run(eval.arena, io, .{
463 .allocator = eval.arena,
464 .argv = argv,517 .argv = argv,
465 .cwd_dir = eval.tmp_dir,518 .cwd_dir = eval.tmp_dir,
466 .cwd = eval.tmp_dir_path,519 .cwd = eval.tmp_dir_path,
...@@ -468,17 +521,17 @@ const Eval = struct {...@@ -468,17 +521,17 @@ const Eval = struct {
468 if (is_foreign) {521 if (is_foreign) {
469 // Chances are the foreign executor isn't available. Skip this evaluation.522 // Chances are the foreign executor isn't available. Skip this evaluation.
470 if (eval.allow_stderr) {523 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}", .{
472 update.name,525 update.name,
473 binary_path,526 binary_path,
474 try eval.target.resolved.zigTriple(eval.arena),527 try eval.target.resolved.zigTriple(eval.arena),
475 @errorName(err),528 err,
476 });529 });
477 }530 }
478 return;531 return;
479 }532 }
480 eval.fatal("update '{s}': failed to run the generated executable '{s}': {s}", .{533 eval.fatal("update '{s}': failed to run the generated executable '{s}': {t}", .{
481 update.name, binary_path, @errorName(err),534 update.name, binary_path, err,
482 });535 });
483 };536 };
484537
...@@ -514,11 +567,12 @@ const Eval = struct {...@@ -514,11 +567,12 @@ const Eval = struct {
514 }567 }
515568
516 fn requestUpdate(eval: *Eval) !void {569 fn requestUpdate(eval: *Eval) !void {
570 const io = eval.io;
517 const header: std.zig.Client.Message.Header = .{571 const header: std.zig.Client.Message.Header = .{
518 .tag = .update,572 .tag = .update,
519 .bytes_len = 0,573 .bytes_len = 0,
520 };574 };
521 var w = eval.child.stdin.?.writer(&.{});575 var w = eval.child.stdin.?.writer(io, &.{});
522 w.interface.writeStruct(header, .little) catch |err| switch (err) {576 w.interface.writeStruct(header, .little) catch |err| switch (err) {
523 error.WriteFailed => return w.err.?,577 error.WriteFailed => return w.err.?,
524 };578 };
...@@ -552,16 +606,13 @@ const Eval = struct {...@@ -552,16 +606,13 @@ const Eval = struct {
552 try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path });606 try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path });
553 defer eval.cc_child_args.items.len -= 2;607 defer eval.cc_child_args.items.len -= 2;
554608
555 const result = std.process.Child.run(.{609 const result = std.process.Child.run(eval.arena, eval.io, .{
556 .allocator = eval.arena,
557 .argv = eval.cc_child_args.items,610 .argv = eval.cc_child_args.items,
558 .cwd_dir = eval.tmp_dir,611 .cwd_dir = eval.tmp_dir,
559 .cwd = eval.tmp_dir_path,612 .cwd = eval.tmp_dir_path,
560 .progress_node = child_prog_node,613 .progress_node = child_prog_node,
561 }) catch |err| {614 }) catch |err| {
562 eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{615 eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {t}", .{ update.name, c_path, err });
563 update.name, c_path, @errorName(err),
564 });
565 };616 };
566 switch (result.term) {617 switch (result.term) {
567 .Exited => |code| if (code != 0) {618 .Exited => |code| if (code != 0) {
...@@ -588,12 +639,13 @@ const Eval = struct {...@@ -588,12 +639,13 @@ const Eval = struct {
588 }639 }
589640
590 fn fatal(eval: *Eval, comptime fmt: []const u8, args: anytype) noreturn {641 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);
592 if (!eval.preserve_tmp_on_fatal) {644 if (!eval.preserve_tmp_on_fatal) {
593 // Kill the child since it holds an open handle to its CWD which is the tmp dir path645 // Kill the child since it holds an open handle to its CWD which is the tmp dir path
594 _ = eval.child.kill() catch {};646 _ = eval.child.kill(io) catch {};
595 std.fs.cwd().deleteTree(eval.tmp_dir_path) catch |err| {647 Dir.cwd().deleteTree(io, eval.tmp_dir_path) catch |err| {
596 std.log.warn("failed to delete tree '{s}': {s}", .{ eval.tmp_dir_path, @errorName(err) });648 std.log.warn("failed to delete tree '{s}': {t}", .{ eval.tmp_dir_path, err });
597 };649 };
598 }650 }
599 std.process.fatal(fmt, args);651 std.process.fatal(fmt, args);
...@@ -759,7 +811,7 @@ const Case = struct {...@@ -759,7 +811,7 @@ const Case = struct {
759 if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n});811 if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n});
760 last_update.outcome = .{812 last_update.outcome = .{
761 .stdout = std.zig.string_literal.parseAlloc(arena, val) catch |err| {813 .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 });
763 },815 },
764 };816 };
765 } else if (std.mem.eql(u8, key, "expect_error")) {817 } else if (std.mem.eql(u8, key, "expect_error")) {
...@@ -833,27 +885,29 @@ const Case = struct {...@@ -833,27 +885,29 @@ const Case = struct {
833885
834fn requestExit(child: *std.process.Child, eval: *Eval) void {886fn requestExit(child: *std.process.Child, eval: *Eval) void {
835 if (child.stdin == null) return;887 if (child.stdin == null) return;
888 const io = eval.io;
836889
837 const header: std.zig.Client.Message.Header = .{890 const header: std.zig.Client.Message.Header = .{
838 .tag = .exit,891 .tag = .exit,
839 .bytes_len = 0,892 .bytes_len = 0,
840 };893 };
841 var w = eval.child.stdin.?.writer(&.{});894 var w = eval.child.stdin.?.writer(io, &.{});
842 w.interface.writeStruct(header, .little) catch |err| switch (err) {895 w.interface.writeStruct(header, .little) catch |err| switch (err) {
843 error.WriteFailed => switch (w.err.?) {896 error.WriteFailed => switch (w.err.?) {
844 error.BrokenPipe => {},897 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}),
846 },899 },
847 };900 };
848901
849 // Send EOF to stdin.902 // Send EOF to stdin.
850 child.stdin.?.close();903 child.stdin.?.close(io);
851 child.stdin = null;904 child.stdin = null;
852}905}
853906
854fn waitChild(child: *std.process.Child, eval: *Eval) void {907fn waitChild(child: *std.process.Child, eval: *Eval) void {
908 const io = eval.io;
855 requestExit(child, eval);909 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});
857 switch (term) {911 switch (term) {
858 .Exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}),912 .Exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}),
859 .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}),913 .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}),
tools/migrate_langref.zig+19-16
...@@ -1,13 +1,16 @@...@@ -1,13 +1,16 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
3const fs = std.fs;2
3const std = @import("std");
4const Io = std.Io;
5const Dir = std.Io.Dir;
4const print = std.debug.print;6const print = std.debug.print;
5const mem = std.mem;7const mem = std.mem;
6const testing = std.testing;8const testing = std.testing;
7const Allocator = std.mem.Allocator;9const Allocator = std.mem.Allocator;
8const max_doc_file_size = 10 * 1024 * 1024;
9const fatal = std.process.fatal;10const fatal = std.process.fatal;
1011
12const max_doc_file_size = 10 * 1024 * 1024;
13
11pub fn main() !void {14pub fn main() !void {
12 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);15 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
13 defer arena_instance.deinit();16 defer arena_instance.deinit();
...@@ -19,27 +22,27 @@ pub fn main() !void {...@@ -19,27 +22,27 @@ pub fn main() !void {
19 const input_file = args[1];22 const input_file = args[1];
20 const output_file = args[2];23 const output_file = args[2];
2124
22 var threaded: std.Io.Threaded = .init(gpa);25 var threaded: std.Io.Threaded = .init(gpa, .{});
23 defer threaded.deinit();26 defer threaded.deinit();
24 const io = threaded.io();27 const io = threaded.io();
2528
26 var in_file = try fs.cwd().openFile(input_file, .{ .mode = .read_only });29 var in_file = try Dir.cwd().openFile(io, input_file, .{ .mode = .read_only });
27 defer in_file.close();30 defer in_file.close(io);
2831
29 var out_file = try fs.cwd().createFile(output_file, .{});32 var out_file = try Dir.cwd().createFile(io, output_file, .{});
30 defer out_file.close();33 defer out_file.close(io);
31 var out_file_buffer: [4096]u8 = undefined;34 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).?, .{});37 var out_dir = try Dir.cwd().openDir(io, Dir.path.dirname(output_file).?, .{});
35 defer out_dir.close();38 defer out_dir.close(io);
3639
37 var in_file_reader = in_file.reader(io, &.{});40 var in_file_reader = in_file.reader(io, &.{});
38 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .unlimited);41 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .unlimited);
3942
40 var tokenizer = Tokenizer.init(input_file, input_file_bytes);43 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
44 try out_file_writer.end();47 try out_file_writer.end();
45}48}
...@@ -266,7 +269,7 @@ const Code = struct {...@@ -266,7 +269,7 @@ const Code = struct {
266 };269 };
267};270};
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 {
270 while (true) {273 while (true) {
271 const token = tokenizer.next();274 const token = tokenizer.next();
272 switch (token.id) {275 switch (token.id) {
...@@ -384,12 +387,12 @@ fn walk(arena: Allocator, tokenizer: *Tokenizer, out_dir: std.fs.Dir, w: anytype...@@ -384,12 +387,12 @@ fn walk(arena: Allocator, tokenizer: *Tokenizer, out_dir: std.fs.Dir, w: anytype
384387
385 const basename = try std.fmt.allocPrint(arena, "{s}.zig", .{name});388 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| {
388 fatal("unable to create file '{s}': {s}", .{ name, @errorName(err) });391 fatal("unable to create file '{s}': {s}", .{ name, @errorName(err) });
389 };392 };
390 defer file.close();393 defer file.close(io);
391 var file_buffer: [1024]u8 = undefined;394 var file_buffer: [1024]u8 = undefined;
392 var file_writer = file.writer(&file_buffer);395 var file_writer = file.writer(io, &file_buffer);
393 const code = &file_writer.interface;396 const code = &file_writer.interface;
394397
395 const source = tokenizer.buffer[source_token.start..source_token.end];398 const source = tokenizer.buffer[source_token.start..source_token.end];
tools/process_headers.zig+22-17
...@@ -12,6 +12,8 @@...@@ -12,6 +12,8 @@
12//! You'll then have to manually update Zig source repo with these new files.12//! You'll then have to manually update Zig source repo with these new files.
1313
14const std = @import("std");14const std = @import("std");
15const Io = std.Io;
16const Dir = std.Io.Dir;
15const Arch = std.Target.Cpu.Arch;17const Arch = std.Target.Cpu.Arch;
16const Abi = std.Target.Abi;18const Abi = std.Target.Abi;
17const OsTag = std.Target.Os.Tag;19const OsTag = std.Target.Os.Tag;
...@@ -128,6 +130,11 @@ const LibCVendor = enum {...@@ -128,6 +130,11 @@ const LibCVendor = enum {
128pub fn main() !void {130pub fn main() !void {
129 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);131 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
130 const allocator = arena.allocator();132 const allocator = arena.allocator();
133
134 var threaded: Io.Threaded = .init(allocator, .{});
135 defer threaded.deinit();
136 const io = threaded.io();
137
131 const args = try std.process.argsAlloc(allocator);138 const args = try std.process.argsAlloc(allocator);
132 var search_paths = std.array_list.Managed([]const u8).init(allocator);139 var search_paths = std.array_list.Managed([]const u8).init(allocator);
133 var opt_out_dir: ?[]const u8 = null;140 var opt_out_dir: ?[]const u8 = null;
...@@ -232,28 +239,28 @@ pub fn main() !void {...@@ -232,28 +239,28 @@ pub fn main() !void {
232 => &[_][]const u8{ search_path, libc_dir, "usr", "include" },239 => &[_][]const u8{ search_path, libc_dir, "usr", "include" },
233 .musl => &[_][]const u8{ search_path, libc_dir, "usr", "local", "musl", "include" },240 .musl => &[_][]const u8{ search_path, libc_dir, "usr", "local", "musl", "include" },
234 };241 };
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);
236 var dir_stack = std.array_list.Managed([]const u8).init(allocator);243 var dir_stack = std.array_list.Managed([]const u8).init(allocator);
237 try dir_stack.append(target_include_dir);244 try dir_stack.append(target_include_dir);
238245
239 while (dir_stack.pop()) |full_dir_name| {246 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) {
241 error.FileNotFound => continue :search,248 error.FileNotFound => continue :search,
242 error.AccessDenied => continue :search,249 error.AccessDenied => continue :search,
243 else => return err,250 else => return err,
244 };251 };
245 defer dir.close();252 defer dir.close(io);
246253
247 var dir_it = dir.iterate();254 var dir_it = dir.iterate();
248255
249 while (try dir_it.next()) |entry| {256 while (try dir_it.next(io)) |entry| {
250 const full_path = try std.fs.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name });257 const full_path = try Dir.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name });
251 switch (entry.kind) {258 switch (entry.kind) {
252 .directory => try dir_stack.append(full_path),259 .directory => try dir_stack.append(full_path),
253 .file, .sym_link => {260 .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);
255 const max_size = 2 * 1024 * 1024 * 1024;262 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));
257 const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t");264 const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t");
258 total_bytes += raw_bytes.len;265 total_bytes += raw_bytes.len;
259 const hash = try allocator.alloc(u8, 32);266 const hash = try allocator.alloc(u8, 32);
...@@ -266,9 +273,7 @@ pub fn main() !void {...@@ -266,9 +273,7 @@ pub fn main() !void {
266 max_bytes_saved += raw_bytes.len;273 max_bytes_saved += raw_bytes.len;
267 gop.value_ptr.hit_count += 1;274 gop.value_ptr.hit_count += 1;
268 std.debug.print("duplicate: {s} {s} ({B})\n", .{275 std.debug.print("duplicate: {s} {s} ({B})\n", .{
269 libc_dir,276 libc_dir, rel_path, raw_bytes.len,
270 rel_path,
271 raw_bytes.len,
272 });277 });
273 } else {278 } else {
274 gop.value_ptr.* = Contents{279 gop.value_ptr.* = Contents{
...@@ -314,7 +319,7 @@ pub fn main() !void {...@@ -314,7 +319,7 @@ pub fn main() !void {
314 total_bytes,319 total_bytes,
315 total_bytes - max_bytes_saved,320 total_bytes - max_bytes_saved,
316 });321 });
317 try std.fs.cwd().makePath(out_dir);322 try Dir.cwd().createDirPath(io, out_dir);
318323
319 var missed_opportunity_bytes: usize = 0;324 var missed_opportunity_bytes: usize = 0;
320 // iterate path_table. for each path, put all the hashes into a list. sort by hit_count.325 // 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 {...@@ -334,9 +339,9 @@ pub fn main() !void {
334 const best_contents = contents_list.pop().?;339 const best_contents = contents_list.pop().?;
335 if (best_contents.hit_count > 1) {340 if (best_contents.hit_count > 1) {
336 // worth it to make it generic341 // 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.* });342 const full_path = try Dir.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).?);343 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);
339 try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = best_contents.bytes });344 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes });
340 best_contents.is_generic = true;345 best_contents.is_generic = true;
341 while (contents_list.pop()) |contender| {346 while (contents_list.pop()) |contender| {
342 if (contender.hit_count > 1) {347 if (contender.hit_count > 1) {
...@@ -355,9 +360,9 @@ pub fn main() !void {...@@ -355,9 +360,9 @@ pub fn main() !void {
355 if (contents.is_generic) continue;360 if (contents.is_generic) continue;
356361
357 const dest_target = hash_kv.key_ptr.*;362 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.* });363 const full_path = try Dir.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).?);364 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);
360 try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = contents.bytes });365 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes });
361 }366 }
362 }367 }
363}368}
tools/update-linux-headers.zig+23-16
...@@ -15,6 +15,8 @@...@@ -15,6 +15,8 @@
15//! You'll then have to manually update Zig source repo with these new files.15//! You'll then have to manually update Zig source repo with these new files.
1616
17const std = @import("std");17const std = @import("std");
18const Io = std.Io;
19const Dir = std.Io.Dir;
18const Arch = std.Target.Cpu.Arch;20const Arch = std.Target.Cpu.Arch;
19const Abi = std.Target.Abi;21const Abi = std.Target.Abi;
20const assert = std.debug.assert;22const assert = std.debug.assert;
...@@ -142,6 +144,11 @@ const PathTable = std.StringHashMap(*TargetToHash);...@@ -142,6 +144,11 @@ const PathTable = std.StringHashMap(*TargetToHash);
142pub fn main() !void {144pub fn main() !void {
143 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);145 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
144 const arena = arena_state.allocator();146 const arena = arena_state.allocator();
147
148 var threaded: Io.Threaded = .init(arena, .{});
149 defer threaded.deinit();
150 const io = threaded.io();
151
145 const args = try std.process.argsAlloc(arena);152 const args = try std.process.argsAlloc(arena);
146 var search_paths = std.array_list.Managed([]const u8).init(arena);153 var search_paths = std.array_list.Managed([]const u8).init(arena);
147 var opt_out_dir: ?[]const u8 = null;154 var opt_out_dir: ?[]const u8 = null;
...@@ -183,30 +190,30 @@ pub fn main() !void {...@@ -183,30 +190,30 @@ pub fn main() !void {
183 .arch = linux_target.arch,190 .arch = linux_target.arch,
184 };191 };
185 search: for (search_paths.items) |search_path| {192 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, &.{
187 search_path, linux_target.name, "include",194 search_path, linux_target.name, "include",
188 });195 });
189 var dir_stack = std.array_list.Managed([]const u8).init(arena);196 var dir_stack = std.array_list.Managed([]const u8).init(arena);
190 try dir_stack.append(target_include_dir);197 try dir_stack.append(target_include_dir);
191198
192 while (dir_stack.pop()) |full_dir_name| {199 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) {
194 error.FileNotFound => continue :search,201 error.FileNotFound => continue :search,
195 error.AccessDenied => continue :search,202 error.AccessDenied => continue :search,
196 else => return err,203 else => return err,
197 };204 };
198 defer dir.close();205 defer dir.close(io);
199206
200 var dir_it = dir.iterate();207 var dir_it = dir.iterate();
201208
202 while (try dir_it.next()) |entry| {209 while (try dir_it.next(io)) |entry| {
203 const full_path = try std.fs.path.join(arena, &[_][]const u8{ full_dir_name, entry.name });210 const full_path = try Dir.path.join(arena, &[_][]const u8{ full_dir_name, entry.name });
204 switch (entry.kind) {211 switch (entry.kind) {
205 .directory => try dir_stack.append(full_path),212 .directory => try dir_stack.append(full_path),
206 .file => {213 .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);
208 const max_size = 2 * 1024 * 1024 * 1024;215 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));
210 const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t");217 const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t");
211 total_bytes += raw_bytes.len;218 total_bytes += raw_bytes.len;
212 const hash = try arena.alloc(u8, 32);219 const hash = try arena.alloc(u8, 32);
...@@ -253,7 +260,7 @@ pub fn main() !void {...@@ -253,7 +260,7 @@ pub fn main() !void {
253 total_bytes,260 total_bytes,
254 total_bytes - max_bytes_saved,261 total_bytes - max_bytes_saved,
255 });262 });
256 try std.fs.cwd().makePath(out_dir);263 try Dir.cwd().createDirPath(io, out_dir);
257264
258 var missed_opportunity_bytes: usize = 0;265 var missed_opportunity_bytes: usize = 0;
259 // iterate path_table. for each path, put all the hashes into a list. sort by hit_count.266 // 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 {...@@ -273,9 +280,9 @@ pub fn main() !void {
273 const best_contents = contents_list.pop().?;280 const best_contents = contents_list.pop().?;
274 if (best_contents.hit_count > 1) {281 if (best_contents.hit_count > 1) {
275 // worth it to make it generic282 // 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.* });283 const full_path = try Dir.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).?);284 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);
278 try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = best_contents.bytes });285 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes });
279 best_contents.is_generic = true;286 best_contents.is_generic = true;
280 while (contents_list.pop()) |contender| {287 while (contents_list.pop()) |contender| {
281 if (contender.hit_count > 1) {288 if (contender.hit_count > 1) {
...@@ -299,9 +306,9 @@ pub fn main() !void {...@@ -299,9 +306,9 @@ pub fn main() !void {
299 else => @tagName(dest_target.arch),306 else => @tagName(dest_target.arch),
300 };307 };
301 const out_subpath = try std.fmt.allocPrint(arena, "{s}-linux-any", .{arch_name});308 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.* });309 const full_path = try Dir.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).?);310 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);
304 try std.fs.cwd().writeFile(.{ .sub_path = full_path, .data = contents.bytes });311 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes });
305 }312 }
306 }313 }
307314
...@@ -316,8 +323,8 @@ pub fn main() !void {...@@ -316,8 +323,8 @@ pub fn main() !void {
316 "any-linux-any/linux/netfilter_ipv6/ip6t_HL.h",323 "any-linux-any/linux/netfilter_ipv6/ip6t_HL.h",
317 };324 };
318 for (bad_files) |bad_file| {325 for (bad_files) |bad_file| {
319 const full_path = try std.fs.path.join(arena, &[_][]const u8{ out_dir, bad_file });326 const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, bad_file });
320 try std.fs.cwd().deleteFile(full_path);327 try Dir.cwd().deleteFile(io, full_path);
321 }328 }
322}329}
323330
tools/update_clang_options.zig+9-6
...@@ -10,7 +10,7 @@...@@ -10,7 +10,7 @@
10//! would mean that the next parameter specifies the target.10//! would mean that the next parameter specifies the target.
1111
12const std = @import("std");12const std = @import("std");
13const fs = std.fs;13const Io = std.Io;
14const assert = std.debug.assert;14const assert = std.debug.assert;
15const json = std.json;15const json = std.json;
1616
...@@ -634,8 +634,12 @@ pub fn main() anyerror!void {...@@ -634,8 +634,12 @@ pub fn main() anyerror!void {
634 const allocator = arena.allocator();634 const allocator = arena.allocator();
635 const args = try std.process.argsAlloc(allocator);635 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
637 var stdout_buffer: [4000]u8 = undefined;641 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);
639 const stdout = &stdout_writer.interface;643 const stdout = &stdout_writer.interface;
640644
641 if (args.len <= 1) printUsageAndExit(args[0]);645 if (args.len <= 1) printUsageAndExit(args[0]);
...@@ -676,8 +680,7 @@ pub fn main() anyerror!void {...@@ -676,8 +680,7 @@ pub fn main() anyerror!void {
676 try std.fmt.allocPrint(allocator, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}),680 try std.fmt.allocPrint(allocator, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}),
677 };681 };
678682
679 const child_result = try std.process.Child.run(.{683 const child_result = try std.process.Child.run(allocator, io, .{
680 .allocator = allocator,
681 .argv = &child_args,684 .argv = &child_args,
682 .max_output_bytes = 100 * 1024 * 1024,685 .max_output_bytes = 100 * 1024 * 1024,
683 });686 });
...@@ -961,8 +964,8 @@ fn objectLessThan(context: void, a: *json.ObjectMap, b: *json.ObjectMap) bool {...@@ -961,8 +964,8 @@ fn objectLessThan(context: void, a: *json.ObjectMap, b: *json.ObjectMap) bool {
961}964}
962965
963fn printUsageAndExit(arg0: []const u8) noreturn {966fn printUsageAndExit(arg0: []const u8) noreturn {
964 const w, _ = std.debug.lockStderrWriter(&.{});967 const stderr = std.debug.lockStderr(&.{});
965 defer std.debug.unlockStderrWriter();968 const w = &stderr.file_writer.interface;
966 printUsage(w, arg0) catch std.process.exit(2);969 printUsage(w, arg0) catch std.process.exit(2);
967 std.process.exit(1);970 std.process.exit(1);
968}971}
tools/update_cpu_features.zig+22-20
...@@ -1,6 +1,8 @@...@@ -1,6 +1,8 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
3const fs = std.fs;2
3const std = @import("std");
4const Io = std.Io;
5const Dir = std.Io.Dir;
4const mem = std.mem;6const mem = std.mem;
5const json = std.json;7const json = std.json;
6const assert = std.debug.assert;8const assert = std.debug.assert;
...@@ -1890,7 +1892,7 @@ pub fn main() anyerror!void {...@@ -1890,7 +1892,7 @@ pub fn main() anyerror!void {
1890 defer arena_state.deinit();1892 defer arena_state.deinit();
1891 const arena = arena_state.allocator();1893 const arena = arena_state.allocator();
18921894
1893 var threaded: std.Io.Threaded = .init(gpa);1895 var threaded: std.Io.Threaded = .init(gpa, .{});
1894 defer threaded.deinit();1896 defer threaded.deinit();
1895 const io = threaded.io();1897 const io = threaded.io();
18961898
...@@ -1927,26 +1929,26 @@ pub fn main() anyerror!void {...@@ -1927,26 +1929,26 @@ pub fn main() anyerror!void {
1927 // there shouldn't be any more argument after the optional filter1929 // there shouldn't be any more argument after the optional filter
1928 if (args.skip()) usageAndExit(args0, 1);1930 if (args.skip()) usageAndExit(args0, 1);
19291931
1930 var zig_src_dir = try fs.cwd().openDir(zig_src_root, .{});1932 var zig_src_dir = try Dir.cwd().openDir(io, zig_src_root, .{});
1931 defer zig_src_dir.close();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 });
1934 defer root_progress.end();1936 defer root_progress.end();
19351937
1936 var group: std.Io.Group = .init;1938 var group: Io.Group = .init;
1937 defer group.cancel(io);1939 defer group.cancel(io);
19381940
1939 for (targets) |target| {1941 for (targets) |target| {
1940 if (filter) |zig_name| {1942 if (filter) |zig_name| {
1941 if (!std.mem.eql(u8, target.zig_name, zig_name)) continue;1943 if (!std.mem.eql(u8, target.zig_name, zig_name)) continue;
1942 }1944 }
1943 group.async(io, processOneTarget, .{.{1945 group.async(io, processOneTarget, .{ io, .{
1944 .llvm_tblgen_exe = llvm_tblgen_exe,1946 .llvm_tblgen_exe = llvm_tblgen_exe,
1945 .llvm_src_root = llvm_src_root,1947 .llvm_src_root = llvm_src_root,
1946 .zig_src_dir = zig_src_dir,1948 .zig_src_dir = zig_src_dir,
1947 .root_progress = root_progress,1949 .root_progress = root_progress,
1948 .target = target,1950 .target = target,
1949 }});1951 } });
1950 }1952 }
19511953
1952 group.wait(io);1954 group.wait(io);
...@@ -1955,12 +1957,12 @@ pub fn main() anyerror!void {...@@ -1955,12 +1957,12 @@ pub fn main() anyerror!void {
1955const Job = struct {1957const Job = struct {
1956 llvm_tblgen_exe: []const u8,1958 llvm_tblgen_exe: []const u8,
1957 llvm_src_root: []const u8,1959 llvm_src_root: []const u8,
1958 zig_src_dir: std.fs.Dir,1960 zig_src_dir: Dir,
1959 root_progress: std.Progress.Node,1961 root_progress: std.Progress.Node,
1960 target: ArchTarget,1962 target: ArchTarget,
1961};1963};
19621964
1963fn processOneTarget(job: Job) void {1965fn processOneTarget(io: Io, job: Job) void {
1964 errdefer |err| std.debug.panic("panic: {s}", .{@errorName(err)});1966 errdefer |err| std.debug.panic("panic: {s}", .{@errorName(err)});
1965 const target = job.target;1967 const target = job.target;
19661968
...@@ -1992,8 +1994,7 @@ fn processOneTarget(job: Job) void {...@@ -1992,8 +1994,7 @@ fn processOneTarget(job: Job) void {
1992 }),1994 }),
1993 };1995 };
19941996
1995 const child_result = try std.process.Child.run(.{1997 const child_result = try std.process.Child.run(arena, io, .{
1996 .allocator = arena,
1997 .argv = &child_args,1998 .argv = &child_args,
1998 .max_output_bytes = 500 * 1024 * 1024,1999 .max_output_bytes = 500 * 1024 * 1024,
1999 });2000 });
...@@ -2240,15 +2241,15 @@ fn processOneTarget(job: Job) void {...@@ -2240,15 +2241,15 @@ fn processOneTarget(job: Job) void {
22402241
2241 const render_progress = progress_node.start("rendering Zig code", 0);2242 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 var target_dir = try job.zig_src_dir.openDir(io, "lib/std/Target", .{});
2244 defer target_dir.close();2245 defer target_dir.close(io);
22452246
2246 const zig_code_basename = try std.fmt.allocPrint(arena, "{s}.zig", .{target.zig_name});2247 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 var zig_code_file = try target_dir.createFile(io, zig_code_basename, .{});
2248 defer zig_code_file.close();2249 defer zig_code_file.close(io);
22492250
2250 var zig_code_file_buffer: [4096]u8 = undefined;2251 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);
2252 const w = &zig_code_file_writer.interface;2253 const w = &zig_code_file_writer.interface;
22532254
2254 try w.writeAll(2255 try w.writeAll(
...@@ -2424,8 +2425,9 @@ fn processOneTarget(job: Job) void {...@@ -2424,8 +2425,9 @@ fn processOneTarget(job: Job) void {
2424}2425}
24252426
2426fn usageAndExit(arg0: []const u8, code: u8) noreturn {2427fn usageAndExit(arg0: []const u8, code: u8) noreturn {
2427 const stderr, _ = std.debug.lockStderrWriter(&.{});2428 const stderr = std.debug.lockStderr(&.{});
2428 stderr.print(2429 const w = &stderr.file_writer.interface;
2430 w.print(
2429 \\Usage: {s} /path/to/llvm-tblgen /path/git/llvm-project /path/git/zig [zig_name filter]2431 \\Usage: {s} /path/to/llvm-tblgen /path/git/llvm-project /path/git/zig [zig_name filter]
2430 \\2432 \\
2431 \\Updates lib/std/target/<target>.zig from llvm/lib/Target/<Target>/<Target>.td .2433 \\Updates lib/std/target/<target>.zig from llvm/lib/Target/<Target>/<Target>.td .
tools/update_crc_catalog.zig+22-17
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1const std = @import("std");1const std = @import("std");
2const fs = std.fs;2const Io = std.Io;
3const Dir = std.Io.Dir;
3const mem = std.mem;4const mem = std.mem;
4const ascii = std.ascii;5const ascii = std.ascii;
56
...@@ -10,27 +11,31 @@ pub fn main() anyerror!void {...@@ -10,27 +11,31 @@ pub fn main() anyerror!void {
10 defer arena_state.deinit();11 defer arena_state.deinit();
11 const arena = arena_state.allocator();12 const arena = arena_state.allocator();
1213
14 var threaded: Io.Threaded = .init(arena, .{});
15 defer threaded.deinit();
16 const io = threaded.io();
17
13 const args = try std.process.argsAlloc(arena);18 const args = try std.process.argsAlloc(arena);
14 if (args.len <= 1) printUsageAndExit(args[0]);19 if (args.len <= 1) printUsageAndExit(args[0]);
1520
16 const zig_src_root = args[1];21 const zig_src_root = args[1];
17 if (mem.startsWith(u8, zig_src_root, "-")) printUsageAndExit(args[0]);22 if (mem.startsWith(u8, zig_src_root, "-")) printUsageAndExit(args[0]);
1823
19 var zig_src_dir = try fs.cwd().openDir(zig_src_root, .{});24 var zig_src_dir = try Dir.cwd().openDir(io, zig_src_root, .{});
20 defer zig_src_dir.close();25 defer zig_src_dir.close(io);
2126
22 const hash_sub_path = try fs.path.join(arena, &.{ "lib", "std", "hash" });27 const hash_sub_path = try Dir.path.join(arena, &.{ "lib", "std", "hash" });
23 var hash_target_dir = try zig_src_dir.makeOpenPath(hash_sub_path, .{});28 var hash_target_dir = try zig_src_dir.createDirPathOpen(io, hash_sub_path, .{});
24 defer hash_target_dir.close();29 defer hash_target_dir.close(io);
2530
26 const crc_sub_path = try fs.path.join(arena, &.{ "lib", "std", "hash", "crc" });31 const crc_sub_path = try Dir.path.join(arena, &.{ "lib", "std", "hash", "crc" });
27 var crc_target_dir = try zig_src_dir.makeOpenPath(crc_sub_path, .{});32 var crc_target_dir = try zig_src_dir.createDirPathOpen(io, crc_sub_path, .{});
28 defer crc_target_dir.close();33 defer crc_target_dir.close(io);
2934
30 var zig_code_file = try hash_target_dir.createFile("crc.zig", .{});35 var zig_code_file = try hash_target_dir.createFile(io, "crc.zig", .{});
31 defer zig_code_file.close();36 defer zig_code_file.close(io);
32 var zig_code_file_buffer: [4096]u8 = undefined;37 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);
34 const code_writer = &zig_code_file_writer.interface;39 const code_writer = &zig_code_file_writer.interface;
3540
36 try code_writer.writeAll(41 try code_writer.writeAll(
...@@ -51,10 +56,10 @@ pub fn main() anyerror!void {...@@ -51,10 +56,10 @@ pub fn main() anyerror!void {
51 \\56 \\
52 );57 );
5358
54 var zig_test_file = try crc_target_dir.createFile("test.zig", .{});59 var zig_test_file = try crc_target_dir.createFile(io, "test.zig", .{});
55 defer zig_test_file.close();60 defer zig_test_file.close(io);
56 var zig_test_file_buffer: [4096]u8 = undefined;61 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);
58 const test_writer = &zig_test_file_writer.interface;63 const test_writer = &zig_test_file_writer.interface;
5964
60 try test_writer.writeAll(65 try test_writer.writeAll(
...@@ -190,8 +195,8 @@ pub fn main() anyerror!void {...@@ -190,8 +195,8 @@ pub fn main() anyerror!void {
190}195}
191196
192fn printUsageAndExit(arg0: []const u8) noreturn {197fn printUsageAndExit(arg0: []const u8) noreturn {
193 const w, _ = std.debug.lockStderrWriter(&.{});198 const stderr = std.debug.lockStderr(&.{});
194 defer std.debug.unlockStderrWriter();199 const w = &stderr.file_writer.interface;
195 printUsage(w, arg0) catch std.process.exit(2);200 printUsage(w, arg0) catch std.process.exit(2);
196 std.process.exit(1);201 std.process.exit(1);
197}202}
tools/update_freebsd_libc.zig+16-16
...@@ -5,6 +5,7 @@...@@ -5,6 +5,7 @@
5//! `zig run tools/update_freebsd_libc.zig -- ~/Downloads/freebsd-src .`5//! `zig run tools/update_freebsd_libc.zig -- ~/Downloads/freebsd-src .`
66
7const std = @import("std");7const std = @import("std");
8const Io = std.Io;
89
9const exempt_files = [_][]const u8{10const exempt_files = [_][]const u8{
10 // This file is maintained by a separate project and does not come from FreeBSD.11 // This file is maintained by a separate project and does not come from FreeBSD.
...@@ -16,29 +17,31 @@ pub fn main() !void {...@@ -16,29 +17,31 @@ pub fn main() !void {
16 defer arena_instance.deinit();17 defer arena_instance.deinit();
17 const arena = arena_instance.allocator();18 const arena = arena_instance.allocator();
1819
20 var threaded: Io.Threaded = .init(arena, .{});
21 defer threaded.deinit();
22 const io = threaded.io();
23
19 const args = try std.process.argsAlloc(arena);24 const args = try std.process.argsAlloc(arena);
20 const freebsd_src_path = args[1];25 const freebsd_src_path = args[1];
21 const zig_src_path = args[2];26 const zig_src_path = args[2];
2227
23 const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/freebsd", .{zig_src_path});28 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| {30 var dest_dir = Io.Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| {
26 std.log.err("unable to open destination directory '{s}': {s}", .{31 std.log.err("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err });
27 dest_dir_path, @errorName(err),
28 });
29 std.process.exit(1);32 std.process.exit(1);
30 };33 };
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, .{});36 var freebsd_src_dir = try Io.Dir.cwd().openDir(io, freebsd_src_path, .{});
34 defer freebsd_src_dir.close();37 defer freebsd_src_dir.close(io);
3538
36 // Copy updated files from upstream.39 // Copy updated files from upstream.
37 {40 {
38 var walker = try dest_dir.walk(arena);41 var walker = try dest_dir.walk(arena);
39 defer walker.deinit();42 defer walker.deinit();
4043
41 walk: while (try walker.next()) |entry| {44 walk: while (try walker.next(io)) |entry| {
42 if (entry.kind != .file) continue;45 if (entry.kind != .file) continue;
43 if (std.mem.startsWith(u8, entry.basename, ".")) continue;46 if (std.mem.startsWith(u8, entry.basename, ".")) continue;
44 for (exempt_files) |p| {47 for (exempt_files) |p| {
...@@ -46,18 +49,15 @@ pub fn main() !void {...@@ -46,18 +49,15 @@ pub fn main() !void {
46 }49 }
4750
48 std.log.info("updating '{s}/{s}' from '{s}/{s}'", .{51 std.log.info("updating '{s}/{s}' from '{s}/{s}'", .{
49 dest_dir_path, entry.path,52 dest_dir_path, entry.path, freebsd_src_path, entry.path,
50 freebsd_src_path, entry.path,
51 });53 });
5254
53 freebsd_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| {55 freebsd_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| {
54 std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{56 std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{
55 freebsd_src_path, entry.path,57 freebsd_src_path, entry.path, dest_dir_path, entry.path, err,
56 dest_dir_path, entry.path,
57 @errorName(err),
58 });58 });
59 if (err == error.FileNotFound) {59 if (err == error.FileNotFound) {
60 try dest_dir.deleteFile(entry.path);60 try dest_dir.deleteFile(io, entry.path);
61 }61 }
62 };62 };
63 }63 }
tools/update_glibc.zig+28-36
...@@ -7,9 +7,11 @@...@@ -7,9 +7,11 @@
7//! `zig run ../tools/update_glibc.zig -- ~/Downloads/glibc ..`7//! `zig run ../tools/update_glibc.zig -- ~/Downloads/glibc ..`
88
9const std = @import("std");9const std = @import("std");
10const Io = std.Io;
11const Dir = std.Io.Dir;
10const mem = std.mem;12const mem = std.mem;
11const log = std.log;13const log = std.log;
12const fs = std.fs;14const fatal = std.process.fatal;
1315
14const exempt_files = [_][]const u8{16const exempt_files = [_][]const u8{
15 // This file is maintained by a separate project and does not come from glibc.17 // This file is maintained by a separate project and does not come from glibc.
...@@ -41,28 +43,30 @@ pub fn main() !void {...@@ -41,28 +43,30 @@ pub fn main() !void {
41 defer arena_instance.deinit();43 defer arena_instance.deinit();
42 const arena = arena_instance.allocator();44 const arena = arena_instance.allocator();
4345
46 var threaded: Io.Threaded = .init(arena, .{});
47 defer threaded.deinit();
48 const io = threaded.io();
49
44 const args = try std.process.argsAlloc(arena);50 const args = try std.process.argsAlloc(arena);
45 const glibc_src_path = args[1];51 const glibc_src_path = args[1];
46 const zig_src_path = args[2];52 const zig_src_path = args[2];
4753
48 const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/glibc", .{zig_src_path});54 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| {56 var dest_dir = Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| {
51 fatal("unable to open destination directory '{s}': {s}", .{57 fatal("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err });
52 dest_dir_path, @errorName(err),
53 });
54 };58 };
55 defer dest_dir.close();59 defer dest_dir.close(io);
5660
57 var glibc_src_dir = try fs.cwd().openDir(glibc_src_path, .{});61 var glibc_src_dir = try Dir.cwd().openDir(io, glibc_src_path, .{});
58 defer glibc_src_dir.close();62 defer glibc_src_dir.close(io);
5963
60 // Copy updated files from upstream.64 // Copy updated files from upstream.
61 {65 {
62 var walker = try dest_dir.walk(arena);66 var walker = try dest_dir.walk(arena);
63 defer walker.deinit();67 defer walker.deinit();
6468
65 walk: while (try walker.next()) |entry| {69 walk: while (try walker.next(io)) |entry| {
66 if (entry.kind != .file) continue;70 if (entry.kind != .file) continue;
67 if (mem.startsWith(u8, entry.basename, ".")) continue;71 if (mem.startsWith(u8, entry.basename, ".")) continue;
68 for (exempt_files) |p| {72 for (exempt_files) |p| {
...@@ -72,14 +76,12 @@ pub fn main() !void {...@@ -72,14 +76,12 @@ pub fn main() !void {
72 if (mem.endsWith(u8, entry.path, ext)) continue :walk;76 if (mem.endsWith(u8, entry.path, ext)) continue :walk;
73 }77 }
7478
75 glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| {79 glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| {
76 log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{80 log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{
77 glibc_src_path, entry.path,81 glibc_src_path, entry.path, dest_dir_path, entry.path, err,
78 dest_dir_path, entry.path,
79 @errorName(err),
80 });82 });
81 if (err == error.FileNotFound) {83 if (err == error.FileNotFound) {
82 try dest_dir.deleteFile(entry.path);84 try dest_dir.deleteFile(io, entry.path);
83 }85 }
84 };86 };
85 }87 }
...@@ -88,25 +90,23 @@ pub fn main() !void {...@@ -88,25 +90,23 @@ pub fn main() !void {
88 // Warn about duplicated files inside glibc/include/* that can be omitted90 // Warn about duplicated files inside glibc/include/* that can be omitted
89 // because they are already in generic-glibc/*.91 // because they are already in generic-glibc/*.
9092
91 var include_dir = dest_dir.openDir("include", .{ .iterate = true }) catch |err| {93 var include_dir = dest_dir.openDir(io, "include", .{ .iterate = true }) catch |err| {
92 fatal("unable to open directory '{s}/include': {s}", .{94 fatal("unable to open directory '{s}/include': {t}", .{ dest_dir_path, err });
93 dest_dir_path, @errorName(err),
94 });
95 };95 };
96 defer include_dir.close();96 defer include_dir.close(io);
9797
98 const generic_glibc_path = try std.fmt.allocPrint(98 const generic_glibc_path = try std.fmt.allocPrint(
99 arena,99 arena,
100 "{s}/lib/libc/include/generic-glibc",100 "{s}/lib/libc/include/generic-glibc",
101 .{zig_src_path},101 .{zig_src_path},
102 );102 );
103 var generic_glibc_dir = try fs.cwd().openDir(generic_glibc_path, .{});103 var generic_glibc_dir = try Dir.cwd().openDir(io, generic_glibc_path, .{});
104 defer generic_glibc_dir.close();104 defer generic_glibc_dir.close(io);
105105
106 var walker = try include_dir.walk(arena);106 var walker = try include_dir.walk(arena);
107 defer walker.deinit();107 defer walker.deinit();
108108
109 walk: while (try walker.next()) |entry| {109 walk: while (try walker.next(io)) |entry| {
110 if (entry.kind != .file) continue;110 if (entry.kind != .file) continue;
111 if (mem.startsWith(u8, entry.basename, ".")) continue;111 if (mem.startsWith(u8, entry.basename, ".")) continue;
112 for (exempt_files) |p| {112 for (exempt_files) |p| {
...@@ -116,23 +116,21 @@ pub fn main() !void {...@@ -116,23 +116,21 @@ pub fn main() !void {
116 const max_file_size = 10 * 1024 * 1024;116 const max_file_size = 10 * 1024 * 1024;
117117
118 const generic_glibc_contents = generic_glibc_dir.readFileAlloc(118 const generic_glibc_contents = generic_glibc_dir.readFileAlloc(
119 io,
119 entry.path,120 entry.path,
120 arena,121 arena,
121 .limited(max_file_size),122 .limited(max_file_size),
122 ) catch |err| switch (err) {123 ) catch |err| switch (err) {
123 error.FileNotFound => continue,124 error.FileNotFound => continue,
124 else => |e| fatal("unable to load '{s}/include/{s}': {s}", .{125 else => |e| fatal("unable to load '{s}/include/{s}': {t}", .{ generic_glibc_path, entry.path, e }),
125 generic_glibc_path, entry.path, @errorName(e),
126 }),
127 };126 };
128 const glibc_include_contents = include_dir.readFileAlloc(127 const glibc_include_contents = include_dir.readFileAlloc(
128 io,
129 entry.path,129 entry.path,
130 arena,130 arena,
131 .limited(max_file_size),131 .limited(max_file_size),
132 ) catch |err| {132 ) catch |err| {
133 fatal("unable to load '{s}/include/{s}': {s}", .{133 fatal("unable to load '{s}/include/{s}': {t}", .{ dest_dir_path, entry.path, err });
134 dest_dir_path, entry.path, @errorName(err),
135 });
136 };134 };
137135
138 const whitespace = " \r\n\t";136 const whitespace = " \r\n\t";
...@@ -140,14 +138,8 @@ pub fn main() !void {...@@ -140,14 +138,8 @@ pub fn main() !void {
140 const glibc_include_trimmed = mem.trim(u8, glibc_include_contents, whitespace);138 const glibc_include_trimmed = mem.trim(u8, glibc_include_contents, whitespace);
141 if (mem.eql(u8, generic_glibc_trimmed, glibc_include_trimmed)) {139 if (mem.eql(u8, generic_glibc_trimmed, glibc_include_trimmed)) {
142 log.warn("same contents: '{s}/include/{s}' and '{s}/include/{s}'", .{140 log.warn("same contents: '{s}/include/{s}' and '{s}/include/{s}'", .{
143 generic_glibc_path, entry.path,141 generic_glibc_path, entry.path, dest_dir_path, entry.path,
144 dest_dir_path, entry.path,
145 });142 });
146 }143 }
147 }144 }
148}145}
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 @@...@@ -1,35 +1,41 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
3const Dir = std.Io.Dir;
24
3pub fn main() !void {5pub fn main() !void {
4 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);6 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
5 defer arena_instance.deinit();7 defer arena_instance.deinit();
6 const arena = arena_instance.allocator();8 const arena = arena_instance.allocator();
79
10 var threaded: Io.Threaded = .init(arena, .{});
11 defer threaded.deinit();
12 const io = threaded.io();
13
8 const args = try std.process.argsAlloc(arena);14 const args = try std.process.argsAlloc(arena);
9 const zig_src_lib_path = args[1];15 const zig_src_lib_path = args[1];
10 const mingw_src_path = args[2];16 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, &.{
13 zig_src_lib_path, "libc", "mingw",19 zig_src_lib_path, "libc", "mingw",
14 });20 });
15 const src_mingw_crt_path = try std.fs.path.join(arena, &.{21 const src_mingw_crt_path = try Dir.path.join(arena, &.{
16 mingw_src_path, "mingw-w64-crt",22 mingw_src_path, "mingw-w64-crt",
17 });23 });
1824
19 // Update only the set of existing files we have already chosen to include25 // Update only the set of existing files we have already chosen to include
20 // in zig's installation.26 // in zig's installation.
2127
22 var dest_crt_dir = std.fs.cwd().openDir(dest_mingw_crt_path, .{ .iterate = true }) catch |err| {28 var dest_crt_dir = Dir.cwd().openDir(io, dest_mingw_crt_path, .{ .iterate = true }) catch |err| {
23 std.log.err("unable to open directory '{s}': {s}", .{ dest_mingw_crt_path, @errorName(err) });29 std.log.err("unable to open directory '{s}': {t}", .{ dest_mingw_crt_path, err });
24 std.process.exit(1);30 std.process.exit(1);
25 };31 };
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| {34 var src_crt_dir = Dir.cwd().openDir(io, src_mingw_crt_path, .{ .iterate = true }) catch |err| {
29 std.log.err("unable to open directory '{s}': {s}", .{ src_mingw_crt_path, @errorName(err) });35 std.log.err("unable to open directory '{s}': {t}", .{ src_mingw_crt_path, err });
30 std.process.exit(1);36 std.process.exit(1);
31 };37 };
32 defer src_crt_dir.close();38 defer src_crt_dir.close(io);
3339
34 {40 {
35 var walker = try dest_crt_dir.walk(arena);41 var walker = try dest_crt_dir.walk(arena);
...@@ -37,10 +43,10 @@ pub fn main() !void {...@@ -37,10 +43,10 @@ pub fn main() !void {
3743
38 var fail = false;44 var fail = false;
3945
40 while (try walker.next()) |entry| {46 while (try walker.next(io)) |entry| {
41 if (entry.kind != .file) continue;47 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) {
44 error.FileNotFound => {50 error.FileNotFound => {
45 const keep = for (kept_crt_files) |item| {51 const keep = for (kept_crt_files) |item| {
46 if (std.mem.eql(u8, entry.path, item)) break true;52 if (std.mem.eql(u8, entry.path, item)) break true;
...@@ -49,11 +55,11 @@ pub fn main() !void {...@@ -49,11 +55,11 @@ pub fn main() !void {
4955
50 if (!keep) {56 if (!keep) {
51 std.log.warn("deleting {s}", .{entry.path});57 std.log.warn("deleting {s}", .{entry.path});
52 try dest_crt_dir.deleteFile(entry.path);58 try dest_crt_dir.deleteFile(io, entry.path);
53 }59 }
54 },60 },
55 else => {61 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 });
57 fail = true;63 fail = true;
58 },64 },
59 };65 };
...@@ -63,24 +69,24 @@ pub fn main() !void {...@@ -63,24 +69,24 @@ pub fn main() !void {
63 }69 }
6470
65 {71 {
66 const dest_mingw_winpthreads_path = try std.fs.path.join(arena, &.{72 const dest_mingw_winpthreads_path = try Dir.path.join(arena, &.{
67 zig_src_lib_path, "libc", "mingw", "winpthreads",73 zig_src_lib_path, "libc", "mingw", "winpthreads",
68 });74 });
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, &.{
70 mingw_src_path, "mingw-w64-libraries", "winpthreads", "src",76 mingw_src_path, "mingw-w64-libraries", "winpthreads", "src",
71 });77 });
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| {
74 std.log.err("unable to open directory '{s}': {s}", .{ dest_mingw_winpthreads_path, @errorName(err) });80 std.log.err("unable to open directory '{s}': {s}", .{ dest_mingw_winpthreads_path, @errorName(err) });
75 std.process.exit(1);81 std.process.exit(1);
76 };82 };
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| {
80 std.log.err("unable to open directory '{s}': {s}", .{ src_mingw_libraries_winpthreads_src_path, @errorName(err) });86 std.log.err("unable to open directory '{s}': {s}", .{ src_mingw_libraries_winpthreads_src_path, @errorName(err) });
81 std.process.exit(1);87 std.process.exit(1);
82 };88 };
83 defer src_winpthreads_dir.close();89 defer src_winpthreads_dir.close(io);
8490
85 {91 {
86 var walker = try dest_winpthreads_dir.walk(arena);92 var walker = try dest_winpthreads_dir.walk(arena);
...@@ -88,16 +94,16 @@ pub fn main() !void {...@@ -88,16 +94,16 @@ pub fn main() !void {
8894
89 var fail = false;95 var fail = false;
9096
91 while (try walker.next()) |entry| {97 while (try walker.next(io)) |entry| {
92 if (entry.kind != .file) continue;98 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) {
95 error.FileNotFound => {101 error.FileNotFound => {
96 std.log.warn("deleting {s}", .{entry.path});102 std.log.warn("deleting {s}", .{entry.path});
97 try dest_winpthreads_dir.deleteFile(entry.path);103 try dest_winpthreads_dir.deleteFile(io, entry.path);
98 },104 },
99 else => {105 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 });
101 fail = true;107 fail = true;
102 },108 },
103 };109 };
...@@ -114,17 +120,17 @@ pub fn main() !void {...@@ -114,17 +120,17 @@ pub fn main() !void {
114120
115 var fail = false;121 var fail = false;
116122
117 while (try walker.next()) |entry| {123 while (try walker.next(io)) |entry| {
118 switch (entry.kind) {124 switch (entry.kind) {
119 .directory => {125 .directory => {
120 switch (entry.depth()) {126 switch (entry.depth()) {
121 1 => if (def_dirs.has(entry.basename)) {127 1 => if (def_dirs.has(entry.basename)) {
122 try walker.enter(entry);128 try walker.enter(io, entry);
123 continue;129 continue;
124 },130 },
125 else => {131 else => {
126 // The top-level directory was already validated132 // The top-level directory was already validated
127 try walker.enter(entry);133 try walker.enter(io, entry);
128 continue;134 continue;
129 },135 },
130 }136 }
...@@ -151,20 +157,20 @@ pub fn main() !void {...@@ -151,20 +157,20 @@ pub fn main() !void {
151 if (std.mem.endsWith(u8, entry.basename, "_onecore.def"))157 if (std.mem.endsWith(u8, entry.basename, "_onecore.def"))
152 continue;158 continue;
153159
154 src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, .{}) catch |err| {160 src_crt_dir.copyFile(entry.path, dest_crt_dir, entry.path, io, .{}) catch |err| {
155 std.log.err("unable to copy {s}: {s}", .{ entry.path, @errorName(err) });161 std.log.err("unable to copy {s}: {t}", .{ entry.path, err });
156 fail = true;162 fail = true;
157 };163 };
158 }164 }
159 if (fail) std.process.exit(1);165 if (fail) std.process.exit(1);
160 }166 }
161167
162 return std.process.cleanExit();168 return std.process.cleanExit(io);
163}169}
164170
165const kept_crt_files = [_][]const u8{171const kept_crt_files = [_][]const u8{
166 "COPYING",172 "COPYING",
167 "include" ++ std.fs.path.sep_str ++ "config.h",173 "include" ++ Dir.path.sep_str ++ "config.h",
168};174};
169175
170const def_exts = [_][]const u8{176const def_exts = [_][]const u8{
tools/update_netbsd_libc.zig+15-14
...@@ -5,6 +5,7 @@...@@ -5,6 +5,7 @@
5//! `zig run tools/update_netbsd_libc.zig -- ~/Downloads/netbsd-src .`5//! `zig run tools/update_netbsd_libc.zig -- ~/Downloads/netbsd-src .`
66
7const std = @import("std");7const std = @import("std");
8const Io = std.Io;
89
9const exempt_files = [_][]const u8{10const exempt_files = [_][]const u8{
10 // This file is maintained by a separate project and does not come from NetBSD.11 // This file is maintained by a separate project and does not come from NetBSD.
...@@ -16,29 +17,31 @@ pub fn main() !void {...@@ -16,29 +17,31 @@ pub fn main() !void {
16 defer arena_instance.deinit();17 defer arena_instance.deinit();
17 const arena = arena_instance.allocator();18 const arena = arena_instance.allocator();
1819
20 var threaded: Io.Threaded = .init(arena, .{});
21 defer threaded.deinit();
22 const io = threaded.io();
23
19 const args = try std.process.argsAlloc(arena);24 const args = try std.process.argsAlloc(arena);
20 const netbsd_src_path = args[1];25 const netbsd_src_path = args[1];
21 const zig_src_path = args[2];26 const zig_src_path = args[2];
2227
23 const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/netbsd", .{zig_src_path});28 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| {30 var dest_dir = Io.Dir.cwd().openDir(io, dest_dir_path, .{ .iterate = true }) catch |err| {
26 std.log.err("unable to open destination directory '{s}': {s}", .{31 std.log.err("unable to open destination directory '{s}': {t}", .{ dest_dir_path, err });
27 dest_dir_path, @errorName(err),
28 });
29 std.process.exit(1);32 std.process.exit(1);
30 };33 };
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, .{});36 var netbsd_src_dir = try Io.Dir.cwd().openDir(io, netbsd_src_path, .{});
34 defer netbsd_src_dir.close();37 defer netbsd_src_dir.close(io);
3538
36 // Copy updated files from upstream.39 // Copy updated files from upstream.
37 {40 {
38 var walker = try dest_dir.walk(arena);41 var walker = try dest_dir.walk(arena);
39 defer walker.deinit();42 defer walker.deinit();
4043
41 walk: while (try walker.next()) |entry| {44 walk: while (try walker.next(io)) |entry| {
42 if (entry.kind != .file) continue;45 if (entry.kind != .file) continue;
43 if (std.mem.startsWith(u8, entry.basename, ".")) continue;46 if (std.mem.startsWith(u8, entry.basename, ".")) continue;
44 for (exempt_files) |p| {47 for (exempt_files) |p| {
...@@ -50,14 +53,12 @@ pub fn main() !void {...@@ -50,14 +53,12 @@ pub fn main() !void {
50 netbsd_src_path, entry.path,53 netbsd_src_path, entry.path,
51 });54 });
5255
53 netbsd_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| {56 netbsd_src_dir.copyFile(entry.path, dest_dir, entry.path, io, .{}) catch |err| {
54 std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{57 std.log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {t}", .{
55 netbsd_src_path, entry.path,58 netbsd_src_path, entry.path, dest_dir_path, entry.path, err,
56 dest_dir_path, entry.path,
57 @errorName(err),
58 });59 });
59 if (err == error.FileNotFound) {60 if (err == error.FileNotFound) {
60 try dest_dir.deleteFile(entry.path);61 try dest_dir.deleteFile(io, entry.path);
61 }62 }
62 };63 };
63 }64 }